动态规划—石子合并(直线和环)

Posted hailin545

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了动态规划—石子合并(直线和环)相关的知识,希望对你有一定的参考价值。

先来看直线的:

 
N堆石子摆成一条线。现要将石子有次序地合并成一堆。规定每次只能选相邻的2堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的代价。计算将N堆石子合并成一堆的最小代价。
 
例如: 1 2 3 4,有不少合并方法
1 2 3 4 => 3 3 4(3) => 6 4(9) => 10(19)
1 2 3 4 => 1 5 4(5) => 1 9(14) => 10(24)
1 2 3 4 => 1 2 7(7) => 3 7(10) => 10(20)
 
括号里面为总代价可以看出,第一种方法的代价最低,现在给出n堆石子的数量,计算最小合并代价。

Input

第1行:N(2 <= N <= 100) 第2 - N + 1:N堆石子的数量(1 <= Aii <= 10000)

Output

输出最小合并代价

Sample Input

4
1
2
3
4

Sample Output

19

 虽然知道这是一道dp题目,但是刚开始的时候怎么也想不出怎么做状态转移,于是又是blog和csdn一顿搜,终于搞出来了。

我们设dp[i][j]是从第i堆石子到第j堆石子合并的最小代价,sum[i]是前i堆石子的数量之和,那么状态转移方程就是:

dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1])

就是把第i堆到第j堆石子分成第i堆到第k堆石子、第k+1堆到第j堆石子,再加上这两大堆石子合并(sum[j]-sum[i-1]),在i<=k<j的范围遍历,找到最小值即可。

由于d[i][j]依赖于d[i][k]和d[k+1][j],所以我们要根据len(j-i)的大小从小到大来循环,这样才能每次都能确保d[i][j]分解的d[i][k]和d[k+1][j]在这之前计算过。

具体细节看代码:

 1 #include<iostream>
 2 #include<cstring>
 3 using namespace std;
 4 #define inf 0x3f3f3f3f
 5 const int maxn=101;
 6 int n,a[maxn],dp[maxn][maxn],sum[maxn];
 7 int main()
 8 {
 9   while(cin>>n)
10   {
11 
12     for(int i=1;i<=n;i++)
13     {
14       cin>>a[i];
15       sum[i]=sum[i-1]+a[i];
16     }
17     for(int i=1;i<=n;i++)
18     {
19       for(int j=1;j<=n;j++)
20       {
21         dp[i][j]=i==j?0:inf;
22       }
23     }
24     for(int len=1;len<n;len++)
25     {
26       for(int i=1;i+len<=n;i++)
27       {
28         int j=i+len;
29         for(int k=i;k<j;k++)
30         {
31           dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]);
32         }
33       }
34     }
35     cout<<dp[1][n]<<endl;
36   }
37 }

 

如果石堆摆成环状呢?

思路就是把它拆成直线型的,比如说题目的1234,我们把它拆成线性的以后就是1234123,然后在从这里找出合并相邻的4堆石子的最小代价就行了,做法跟原来的差不多,不过最后再搜一个最小值,洛谷1880就有这道题,不过洛谷的题还要求最大代价,这是我a了洛谷的代码:

 

 1 #include<iostream>
 2 #include<cstring>
 3 #define inf 0x3f3f3f3f
 4 using namespace std;
 5 const int maxn=2e3+5;
 6 int n,a[maxn],dp_min[maxn][1001],dp_max[maxn][1001],sum[maxn],s=inf;
 7 int main()
 8 {
 9   cin>>n;
10   for(int i=1;i<=n;i++)
11   {
12     cin>>a[i];
13     sum[i]=sum[i-1]+a[i];
14   }
15   for(int i=n+1;i<=n*2-1;i++)
16   {
17     a[i]=a[i-n];
18     sum[i]=sum[i-1]+a[i];
19   }
20   for(int len=1;len<n;len++)
21   {
22     for(int i=1;i+len<=2*n;i++)
23     {
24       int j=i+len;
25       dp_min[i][j]=inf;
26       for(int k=i;k<j;k++)
27       {
28         dp_min[i][j]=min(dp_min[i][j],dp_min[i][k]+dp_min[k+1][j]+sum[j]-sum[i-1]);
29         dp_max[i][j]=max(dp_max[i][j],dp_max[i][k]+dp_max[k+1][j]+sum[j]-sum[i-1]);
30       }
31     }
32   }
33   int ans_min=inf,ans_max=0;
34   for(int i=1;i<=n;i++)
35   {
36     ans_min=min(ans_min,dp_min[i][i+n-1]);
37     ans_max=max(ans_max,dp_max[i][i+n-1]);
38   }
39   cout<<ans_min<<endl<<ans_max<<endl;
40 }

 

 

为什么跑到洛谷去a题了?因为这个做法在这道题会T啊<(_ _)>,O(n^3)的算法,n又是1000,难顶。

不过还好,既然有这道题,一定就有解法。在网上查了一通,说是要用什么四边形不等式优化成O(n^2),看的我真的脑壳疼。

不学会怎么行?不过我就算懂了一些,也表达不出来,给你们推荐这个大佬的blog:https://www.cnblogs.com/cglongge/p/9451161.html

 

以上是关于动态规划—石子合并(直线和环)的主要内容,如果未能解决你的问题,请参考以下文章

石子合并(区间动态规划)- NYOJ 737

动态规划之环形石子合并问题

动态规划

区间上的动态规划

51nod 1021 石子归并 (动态规划 简单代码)

动态规划-石子问题