• 【动态规划】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 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

  • 相关阅读:
    php学习【1】
    网页项目源码笔记
    python学习笔记
    php集成开发环境xampp的搭建
    ubuntu18.04.1LTS系统远程工具secureCRT
    关于IT人的一些消遣区
    linux系统的启动过程简要分析
    【shell脚本学习-1】
    Linux命令总结--cat命令
    Linux命令总结--vi/vim命令
  • 原文地址:https://www.cnblogs.com/antiquality/p/9904577.html
Copyright © 2020-2023  润新知