【动态规划】bzoj1705: [Usaco2007 Nov]Telephone Wire 架设电话线

可能是一类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

5 2
2
3
5
1
4
输入说明:
一共有5根电话线杆,在杆间拉电话线的费用是每米高度差$2。
在改造之前,电话线杆的高度依次为2,3,5,1,4米。

Sample Output

15
输出说明:
最好的改造方法是:Farmer John把第一根电话线杆加高1米,把第四根加高2米,
使得它们的高度依次为3,3,5,3,4米。这样花在加高电线杆上的钱是$5。
此时,拉电话线的费用为$2*(0+2+2+1) = $10,总花费为$15。

题目分析

最基础的转移方程

因为这里每一个元素的转移只和前一个有关系,那么自然想到$f[i][j]$表示处理到第$i$个元素,同时它的高度为$j$的最小代价。

那么总状态数是$10^5\times 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\n",ans);
35     return 0;
36 }

从决策单调看转移

因为$f[i][j]=(j-h[i])^2+\{f[i-1][k]+c|j-k|\}$,而大括号内的式子与$j$无关,说明可以在枚举$j$的过程中选择最优的$k$。

至于这个选择也并不难。把式子大力拆开就是

$\begin{equation}\left\{\begin{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}\right.\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\n",ans);
36     return 0;
37 }

 

 

END

posted @ 2018-11-04 16:49  AntiQuality  阅读(287)  评论(0编辑  收藏  举报