斜率优化DP

先考虑朴素DP方程,

f[i][k]代表第k个厂建在i棵树那里的最小代价,最后答案为f[n+1][3];

f[i][k]=min(f[j][k-1] + 把j+1~i的树都运到i的代价)

首先注意到“把j+1~i的树都运到i的代价”不太方便表达,每次都暴力计算显然是无法承受的,

于是考虑前缀和优化,观察到先运到下一棵树那里,等一会再运下去,和直接运下去是等效的。

设sum[i]代表1 ~ i的树都运到i的代价,

于是根据前缀和思想,猜想我们可以用1 ~ r 的代价与 1 ~ l-1的代价获取l ~ r的代价,

所以要做的就是吧1 ~ l-1 对 1 ~ r产生的贡献给算出来,然后减掉,

考虑先把1 ~ l-1的树都运到l-1,所以这部分的代价是sum[l-1],

然后再把树一次性运到r,那么代价是sum_weight[l-1] * (sum_len[r] - sum_len[l-1]);

                                    总的重量 * 现在要再次运的路程

这里为了表示方便,用$sw$代表sum_weight,用$sl$代表sum_len;


于是用$sum[r]$ 减去这两部分代价就可以得到$l ~ r$ 的代价(把$l ~ r$的树都运到$r$)

代价(l ~ r)$ =  sum[r] - sum[l-1] - sw[l-1] * (sl[r] - sl[l-1]);$

那么如何计算sum ?

也是一样的思想,用前面的推后面的,先得到前面的代价,再加上新增的代价即可

$sum[i]=sum[i-1] + swt[i-1] * len[i-1];$//len[i-1]代表i-1到i的距离

于是我们就得到了DP方程:

当$k==1$时,$f[i][k]=sum[i]$;

else

        $f[i][k]=min(f[j][k-1] + sum[i] - sum[j] - sw[j] * (sl[i] - sl[j]));$

但是可以发现,由于k最大就是3,而且3必须是n+1才可以取,

而且当$k==1$时,$f[i][k]$就等于$sum[i]$,

所以考虑优化维数:

当$k==1$时,不用求,因为有$sum$了

当$k==2$时,调用的$f[j][k-1]$替换为$sum[j]$,并且还可以发现由于后面有一个$-sum[j]$,所以可以直接消掉

当$k==3$时,由于只有$n+1$可以取,所以直接在外面多写一个循环,相当于最后统计答案即可

转移方式同朴素方程

但是这样是$n^2$的DP,而$n$有20000,那怎么办呢?

考虑斜率优化。

首先我们用暴力打表可以发现,决策是单调的,

打表代码(朴素DP):

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define R register int
 4 #define AC 20100
 5 int n, ans;
 6 int sum[AC], sum_weight[AC], sum_len[AC], f[AC];
 7 int weight[AC], len[AC];
 8 inline int read()
 9 {
10     int x = 0; char c = getchar();
11     while(c < '0' || c > '9') c = getchar();
12     while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
13     return x; 
14 }
15 
16 void pre()
17 {
18     n = read();
19     for(R i = 1; i <= n; i ++)
20         weight[i] = read(), len[i] = read();
21 }
22 
23 void getsum()
24 {
25     for(R i = 1; i <= n + 1; i ++)//山脚的也要求
26     {
27         sum_len[i] = sum_len[i - 1] + len[i - 1];
28         sum_weight[i] = sum_weight[i - 1] + weight[i];
29         sum[i] = sum[i - 1] + sum_weight[i - 1] * len[i - 1];
30     //    printf("%d : %d\n",i,sum[i]);
31     }
32 }
33 
34 void work()
35 {
36     for(R i = 1; i <= n; i ++)
37     {
38         int tmp = 0;
39         f[i] = INT_MAX;
40         for(R j = 1;j < i;j ++)
41         {
42             if(sum[i] - sum_weight[j] * (sum_len[i] - sum_len[j]) < f[i])
43             {
44                 f[i] = sum[i] - sum_weight[j] * (sum_len[i] - sum_len[j]);
45                 tmp = j;
46             } 
47         }
48         printf("%d --- > %d\n", tmp, i);//打表验证决策单调性
49     }
50     ans = INT_MAX;
51     for(R i = 2; i <= n; i ++)//注意应该是n+1,因为山脚是在下面
52         ans = min(ans, f[i] + sum[n + 1] - sum[i] - sum_weight[i] * (sum_len[n + 1] - sum_len[i]));
53     for(R i = 2; i <= n; i ++) printf("%d : %d\n", i, f[i]);
54     printf("%d\n", ans);
55 }
56 
57 int main()
58 {
59     freopen("in.in", "r", stdin);
60     freopen("out.out", "w", stdout);
61     pre();
62     getsum();
63     work();
64     fclose(stdin);
65     fclose(stdout);
66     return 0;
67 }
View Code

相关文章:

  • 2021-10-21
  • 2022-12-23
  • 2021-11-18
  • 2021-12-28
  • 2021-05-18
  • 2021-11-08
  • 2022-12-23
  • 2021-10-25
猜你喜欢
  • 2022-12-23
  • 2021-11-06
  • 2021-12-27
  • 2021-12-26
  • 2021-08-29
  • 2022-12-23
  • 2022-02-23
相关资源
相似解决方案