zoukankan      html  css  js  c++  java
  • P1880 [NOI1995]石子合并[环形DP]

     题目来源:洛谷

    题目描述

    在一个圆形操场的四周摆放N堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。

    试设计出1个算法,计算出将N堆石子合并成1堆的最小得分和最大得分

    输入输出格式

    输入格式:

    数据的第1行试正整数N,1≤N≤100,表示有N堆石子.第2行有N个数,分别表示每堆石子的个数.

    输出格式:

    输出共2行,第1行为最小得分,第2行为最大得分.

    输入输出样例

     

    输入样例#1: 
    4
    4 5 9 4
    输出样例#1: 
    43
    54

     

    解析:

    这是一道相当经典的四边形不等式和环形dp,以至于一道NOI级别的题目被研究成了绿题。。。


    首先题目意思很清楚,很容易看出来这就是个首尾相接的环。
     
     
    我们先想一下如果不是环,而是一条链怎么做。
     
    dp的思路很容易想出来,就是区间dp的思想,我们将将要合并的两堆石子的距离当做阶段,合并哪两堆石子作为状态,决策就是选择哪两堆石子合并。
     
    我们定义dp[l][r]表示合并l~r之间的石子所需耗费的最小/大费用,其中l表示左端点的石子,r表示右端点的石子。
     
    我们可以把每个l~r的状态看做两个子问题,假设k为l~r之间的任意一堆石子,我们就要使得l~k的答案和k+1~r的答案最优。因此我们枚举l~r之间的每个位置k,并寻找使得结果最优的一个k。
    对应到动态规划中,就意味着两个较小的区间向一个更长的区间转移,k点不仅是两个小区间的划分点,也是转移的决策。
     
    比方说如果我们求最小值,得出这样的状态转移方程:

    dp[l][r]=min(1<=l<=n-len+1,l<r<=n,l<=k<r){dp[l][k]+dp[k+1][r]+w(l,r)};

    其中w(l,r)函数表示合并l~r之间的石子的花费,len表示区间长度

    这个东西叫二维四边形不等式,常用于优化区间dp,详细情况我这里就不讲了(我懒),如果有需要可以百度或者戳这里看dalao博客。

    PS:关于初始化,我们知道每个石子堆移到它自己上面花费是0,所以。。。

    思考一下为什么每次决策增加的是区间和:移动石子堆的花费是可叠加的,而且费用是会重叠的,如果我们要合并l~r之间的所有石子,那么显然无论它之前是由哪两堆合并而来的,花费都是sum[r]-sum[l-1](sum为前缀和数组)。

    关于w(l,r)函数,我们可以用前缀和优化搞定,于是我们需要一个三重循环来解决此问题,复杂度也就差不多达到了O(n^3),这是无法承受大规模数据的。

    动态规划有一种基本的优化思路,就是如果一个状态的某个维度可以由其他维度推出,那么我们直接在每循环的时候把它算出来就得了。

    在这里,如果我们知道了左端点l和区间长度len的话,那么r就是可求得的,即r=l+len-1;

    至此,链式dp的思路就讲完了。


    你如果拿这个思路做这道题,会发现过不了(WA4个点),这是因为题目说了是环。

    那环跟链有啥区别呢?

    很容易发现,如果是环形的话,某两个点之间的距离可以有两种可能。

    举个例子,我们假设石子总堆数为n,对于第一堆石子到最后一堆石子之间的距离len,如果是链式的话,只有n-1这种可能;而如果是环形的话,我们就有n-1和1两种可能。也就是说,每个石子堆之间都有两个距离(当然可以相等),那我们怎么设计dp算法呢?

    有一种普遍的解法,就是把环拆开成相连的两条相同的链,然后在1~2n上跑dp。具体来说其实就是把上面讲到的两个距离拆出来分开处理,某个点l到第一条链有一个最优解,到第二条链又有一个最优解,我们再取这两个最优解的最优解。

    所以之前的步骤我们可以参考链式dp写出来,而寻找两个最优解中的最优解,我们需要再分析一下。

    链式的解法最优解就在dp[1][n],即把1~n堆石子全部合并。注意,这种情况下有且仅有1~n之间的距离为n-1

    然而如果这是一个环,就有n个n-1的距离,也就是有n个局部最优解,我们还要在这些最优解里面找全局最优解,这也是本题的又一个关键点。

    所以实际上我们扫一遍dp[i][i+n-1](1<=i<=n),找到最优解就行了。

    参考代码:

     1 #include<cstdio>
     2 #include<iostream>
     3 #include<cstring>
     4 #define N 1010
     5 #define INF 0x3f3f3f3f
     6 using namespace std;
     7 int a[N<<1],dp1[N][N],dp2[N][N],s[N];
     8 int main()
     9 {
    10     int n;
    11     cin>>n;
    12     for(int i=1;i<=n;i++) scanf("%d",&a[i]),a[i+n]=a[i];
    13     memset(dp1,0x3f,sizeof(dp1));
    14     memset(dp2,0xff,sizeof(dp2));
    15     for(int i=1;i<=n+n;i++){
    16         dp1[i][i]=0;dp2[i][i]=0;
    17         s[i]=s[i-1]+a[i];
    18     }
    19     for(int len=2;len<=n*2;len++)
    20      for(int l=1;l<=n*2-len+1;l++)
    21      {
    22          int r=l+len-1;
    23          for(int k=l;k<r;k++)
    24          {
    25              dp1[l][r]=min(dp1[l][r],dp1[l][k]+dp1[k+1][r]+(s[r]-s[l-1]));
    26              dp2[l][r]=max(dp2[l][r],dp2[l][k]+dp2[k+1][r]+(s[r]-s[l-1]));
    27         }
    28      }
    29     int maxx=-INF,minn=INF;
    30     for(int i=1;i<=n;i++){
    31         maxx=max(maxx,dp2[i][i+n-1]);
    32         minn=min(minn,dp1[i][i+n-1]);
    33     }
    34     cout<<minn<<endl;
    35     cout<<maxx<<endl;
    36     return 0;
    37 }
  • 相关阅读:
    gitignore 过滤文件
    lua语言入门之Sublime Text设置lua的Build System
    进程间通信
    临界区 事件 互斥对象等多线程编程基础
    Delphi通过Map文件查找内存地址出错代码所在行
    Delphi/C++ Builder Map文件格式解析
    深入理解计算机系统----读书笔记
    TCP/IP——内网IP
    Python——import与reload模块的区别
    Linux——grep binary file
  • 原文地址:https://www.cnblogs.com/DarkValkyrie/p/11066088.html
Copyright © 2011-2022 走看看