可能是一类dp的通用优化
Description
最近,Farmer John的奶牛们越来越不满于牛棚里一塌糊涂的电话服务 于是,她们要求FJ把那些老旧的电话线换成性能更好的新电话线。 新的电话线架设在已有的N(2 <= N <= 100,000)根电话线杆上, 第i根电话线杆的高度为height_i米(1 <= height_i <= 100)。 电话线总是从一根电话线杆的顶端被引到相邻的那根的顶端 如果这两根电话线杆的高度不同,那么FJ就必须为此支付 C*电话线杆高度差(1 <= C <= 100)的费用。当然,你不能移动电话线杆, 只能按原有的顺序在相邻杆间架设电话线。Farmer John认为 加高某些电话线杆能减少架设电话线的总花费,尽管这项工作也需要支出一定的费用。 更准确地,如果他把一根电话线杆加高X米的话,他得为此付出X^2的费用。 请你帮Farmer John计算一下,如果合理地进行这两种工作,他最少要在这个电话线改造工程上花多少钱。
Input
* 第1行: 2个用空格隔开的整数:N和C
* 第2..N+1行: 第i+1行仅有一个整数:height_i
Output
* 第1行: 输出Farmer John完成电话线改造工程所需要的最小花费
Sample Input
2
3
5
1
4
输入说明:
一共有5根电话线杆,在杆间拉电话线的费用是每米高度差$2。
在改造之前,电话线杆的高度依次为2,3,5,1,4米。
Sample Output
输出说明:
最好的改造方法是:Farmer John把第一根电话线杆加高1米,把第四根加高2米,
使得它们的高度依次为3,3,5,3,4米。这样花在加高电线杆上的钱是$5。
此时,拉电话线的费用为$2*(0+2+2+1) = $10,总花费为$15。
题目分析
最基础的转移方程
因为这里每一个元素的转移只和前一个有关系,那么自然想到$f[i][j]$表示处理到第$i$个元素,同时它的高度为$j$的最小代价。
那么总状态数是$10^5 imes 10^2$,每一次转移$10^4$。正常代码不刻意卡常是无法通过的。
从数形结合看转移
写下转移方程$f[i][j]=f[i-1][k]+(j-h[i])^2+c|j-k|$发现对于同一$f[i][j]$,其每次转移是一个开口向上的二次函数,这意味着枚举前一个高度$k$时若发现代价随高度递增,那么之后状态的也不可能会更优了。
1 #include<bits/stdc++.h> 2 #define R register int 3 const int maxn = 100035; 4 5 int n,c,h[maxn],mx,ans; 6 int f[2][103],nw; 7 8 inline int abs(int x){return x>0?x:-x;} 9 int main() 10 { 11 memset(f, 0x3f3f3f3f, sizeof f); 12 scanf("%d%d",&n,&c); 13 ans = 0x3f3f3f3f; 14 for (R i=1; i<=n; i++) scanf("%d",&h[i]), mx = h[i]<mx?mx:h[i]; 15 for (R i=h[1]; i<=mx; i++) f[1][i] = (i-h[1])*(i-h[1]); 16 for (R i=2; i<=n; i++) 17 { 18 for (R j=h[i]; j<=mx; j++) 19 { 20 R pre = 0x3f3f3f3f, w = (j-h[i])*(j-h[i]), val = 0; 21 for (R k=h[i-1]; k<=mx; k++) 22 { 23 val = f[nw^1][k]+w+c*abs(j-k); 24 if (val < f[nw][j]) f[nw][j] = val; 25 else if (val > pre) break; 26 pre = val; 27 } 28 } 29 nw ^= 1; 30 memset(f[nw], 0x3f3f3f3f, sizeof f[nw]); 31 } 32 for (R i=h[n]; i<=mx; i++) 33 ans = std::min(ans, f[nw^1][i]); 34 printf("%d ",ans); 35 return 0; 36 }
从决策单调看转移
因为$f[i][j]=(j-h[i])^2+{f[i-1][k]+c|j-k|}$,而大括号内的式子与$j$无关,说明可以在枚举$j$的过程中选择最优的$k$。
至于这个选择也并不难。把式子大力拆开就是
$egin{equation}left{egin{array}{lr}f[i][j]=(j-h[i])^2+min(f[i-1][k]-c*k+c*j) (k<j) &\ f[i][j]=(j-h[i])^2+min(f[i-1][k]+c*k-c*j) (k>j)end{array} ight.end{equation}$
这样就可以分别从小到大和从大到小各枚举一遍,天然保证了$j,k$之间的大小顺序。
做法来源:题解 P2885 【[USACO07NOV]电话线Telephone Wire】
1 #include<bits/stdc++.h> 2 #define R register int 3 const int maxn = 100035; 4 const int INF = 0x3f3f3f3f; 5 6 int n,c,mx,nw,ans,h[maxn]; 7 int f[2][103]; 8 9 inline int min(int a, int b){return a>b?b:a;} 10 int main() 11 { 12 memset(f, 0x3f3f3f3f, sizeof f); 13 scanf("%d%d",&n,&c), ans = INF; 14 for (R i=1; i<=n; i++) scanf("%d",&h[i]), mx = mx>h[i]?mx:h[i]; 15 for (R i=h[1]; i<=mx; i++) f[1][i] = (i-h[1])*(i-h[1]); 16 for (R i=2; i<=n; i++) 17 { 18 R k = INF; 19 for (R j=h[i-1]; j<=mx; j++) 20 { 21 k = min(k, f[nw^1][j]-c*j); 22 if (j >= h[i]) f[nw][j] = k+c*j+(j-h[i])*(j-h[i]); 23 } 24 k = INF; 25 for (R j=mx; j>=h[i]; j--) 26 { 27 k = min(k, f[nw^1][j]+c*j); 28 f[nw][j] = std::min(k-c*j+(h[i]-j)*(h[i]-j), f[nw][j]); 29 } 30 nw ^= 1; 31 memset(f[nw], 0x3f3f3f3f, sizeof f[nw]); 32 } 33 for (R i=h[n]; i<=mx; i++) 34 ans = min(ans, f[nw^1][i]); 35 printf("%d ",ans); 36 return 0; 37 }
END