石子合并问题java_石子合并问题(直线版)

本文介绍了石子合并问题的动态规划解决方案,包括最普通的O(n^3)算法和四边形不等式优化后的O(n^2)算法,以及GarsiaWachs算法在O(nlogn)复杂度下的实现,探讨了算法优化的思路和证明过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目:

石子合并(一)

时间限制:1000 ms  |  内存限制:65535 KB

难度:3

描述有N堆石子排成一排,每堆石子有一定的数量。现要将N堆石子并成为一堆。合并的过程只能每次将相邻的两堆石子堆成一堆,每次合并花费的代价为这两堆石子的和,经过N-1次合并后成为一堆。求出总的代价最小值。

输入有多组测试数据,输入到文件结束。

每组测试数据第一行有一个整数n,表示有n堆石子。

接下来的一行有n(0< n <200)个数,分别表示这n堆石子的数目,用空格隔开输出输出总代价的最小值,占单独的一行样例输入3

1 2 3

7

13 7 8 16 21 4 18样例输出9

239

最普通的算法O(n^3):

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 #include

2 #include

3 #include

4 #include

5 #include

6 #include

7 using namespacestd;8

9 const int N=205;10 const int INF=0x7fffffff;11 intn;12 inta[N],sum[N],dp[N][N];13

14 voidf();15

16 intmain(){17 //freopen("D:\\input.in","r",stdin);

18 while(~scanf("%d",&n)){19 sum[0]=0;20 for(int i=1;i<=n;i++){21 scanf("%d",&a[i]);22 sum[i]=sum[i-1]+a[i];23 }24 f();25 printf("%d\n",dp[1][n]);26 }27 return 0;28 }29 voidf(){30 for(int i=1;i<=n;i++) dp[i][i]=0;31 for(int r=1;rn) break;35 dp[i][j]=INF;36 for(int k=i;k<=j;k++){37 dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]);38 }39 dp[i][j]+=sum[j]-sum[i-1];40 }41 }42 }

224ms

其中,dp[i][j]代表i到j堆的最优值,sum[i]代表第1堆到第i堆的数目总和。有:dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j])+sum[j]-sum[i-1]。

考虑四边形不等式优化:接近O(n^2):

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 #include

2 #include

3 #include

4 #include

5 #include

6 #include

7 using namespacestd;8

9 const int N=205;10 const int INF=0x7fffffff;11 intn;12 inta[N],sum[N],dp[N][N],s[N][N];13

14 voidf();15

16 intmain(){17 //freopen("D:\\input.in","r",stdin);

18 while(~scanf("%d",&n)){19 sum[0]=0;20 for(int i=1;i<=n;i++){21 scanf("%d",&a[i]);22 sum[i]=sum[i-1]+a[i];23 }24 f();25 printf("%d\n",dp[1][n]);26 }27 return 0;28 }29 voidf(){30 for(int i=1;i<=n;i++) dp[i][i]=0,s[i][i]=i;31 for(int r=1;rn) break;35 dp[i][j]=INF;36 for(int k=s[i][j-1];k<=s[i+1][j];k++){37 if(dp[i][j]>dp[i][k]+dp[k+1][j]){38 dp[i][j]=dp[i][k]+dp[k+1][j];39 s[i][j]=k;40 }41 }42 dp[i][j]+=sum[j]-sum[i-1];43 }44 }45 }

32ms

优化:原状态转移方程中的k的枚举范围便可以从原来的(i~j-1)变为(s[i,j-1]~s[i+1,j])。

解释下:

四边形不等式优化动态规划原理:

1.当决策代价函数w[i][j]满足w[i][j]+w[i’][j’]<=w[I;][j]+w[i][j’](i<=i’<=j<=j’)时,称w满足四边形不等式.当函数w[i][j]满足w[i’][j]<=w[i][j’] i<=i’<=j<=j’)时,称w关于区间包含关系单调.

2.如果状态转移方程m为

1.png且决策代价w满足四边形不等式的单调函数(可以推导出m亦为满足四边形不等式的单调函数),则可利用四边形不等式推出最优决策s的单调函数性,从而减少每个状态的状态数,将算法的时间复杂度由原来的O(n^3)降低为O(n^2).方法是通过记录子区间的最优决策来减少当前的决策量.令:

s[i][j]=max{k | ma[i][j] = m[i][k-1] + m[k][j] + w[i][j]}

由于决策s具有单调性,因此状态转移方程可修改为:

2.png

证明过程: (转载)

设m[i,j]表示动态规划的状态量。

m[i,j]有类似如下的状态转移方程:

m[i,j]=opt{m[i,k]+m[k,j]}(i≤k≤j)

如果对于任意的a≤b≤c≤d,有m[a,c]+m[b,d]≤m[a,d]+m[b,c],那么m[i,j]满足四边形不等式。

以上是适用这种优化方法的必要条件

对于一道具体的题目,我们首先要证明它满足这个条件,一般来说用数学归纳法证明,根据题目的不同而不同。

通常的动态规划的复杂度是O(n3),我们可以优化到O(n2)

设s[i,j]为m[i,j]的决策量,即m[i,j]=m[i,s[i,j]]+m[s[i,j]+j]

我们可以证明,s[i,j-1]≤s[i,j]≤s[i+1,j]  (证明过程见下)

那么改变状态转移方程为:

m[i,j]=opt{m[i,k]+m[k,j]}(s[i,j-1]≤k≤s[i+1,j])

复杂度分析:不难看出,复杂度决定于s的值,以求m[i,i+L]为例,

(s[2,L+1]-s[1,L])+(s[3,L+2]-s[2,L+1])…+(s[n-L+1,n]-s[n-L,n-1])=s[n-L+1,n]-s[1,L]≤n

所以总复杂度是O(n2)

对s[i,j-1]≤s[i,j]≤s[i+1,j]的证明:

设mk[i,j]=m[i,k]+m[k,j],s[i,j]=d

对于任意k

(mk[i+1,j]-md[i+1,j])-(mk[i,j]-md[i,j])

=(mk[i+1,j]+md[i,j])-(md[i+1,j]+mk[i,j])

=(m[i+1,k]+m[k,j]+m[i,d]+m[d,j])-(m[i+1,d]+m[d,j]+m[i,k]+m[k,j])

=(m[i+1,k]+m[i,d])-(m[i+1,d]+m[i,k])

∵m满足四边形不等式,∴对于i

∴(mk[i+1,j]-md[i+1,j])≥(mk[i,j]-md[i,j])≥0

∴s[i,j]≤s[i+1,j],同理可证s[i,j-1]≤s[i,j]

证毕

扩展:

以上所给出的状态转移方程只是一种比较一般的,其实,很多状态转移方程都满足四边形不等式优化的条件。

解决这类问题的大概步骤是:

0.证明w满足四边形不等式,这里w是m的附属量,形如m[i,j]=opt{m[i,k]+m[k,j]+w[i,j]},此时大多要先证明w满足条件才能进一步证明m满足条件

1.证明m满足四边形不等式

2.证明s[i,j-1]≤s[i,j]≤s[i+1,j]

GarsiaWachs算法优化:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 #include

2 #include

3 #include

4 using namespacestd;5

6 const int N = 205;7 intstone[N];8 intn,t,ans;9

10 void combine(intk);11

12 intmain(){13 while(cin>>n){14 for(int i=0;i= 3 && stone[t-3] <= stone[t-1])21 combine(t-2);22 }23 while(t > 1)24 combine(t-1);25 printf("%d\n",ans);26 }27 return 0;28 }29 void combine(intk){30 int tmp = stone[k] + stone[k-1];31 ans +=tmp;32 for(int i=k;i0 && stone[j-1] < tmp;j--)37 stone[j] = stone[j-1];38 stone[j] =tmp;39 while(j >= 2 && stone[j] >= stone[j-2]){40 int d = t -j;41 combine(j-1);42 j = t -d;43 }44 }

4ms

解释:

对于石子合并问题,有一个最好的算法,那就是GarsiaWachs算法。时间复杂度为O(n^2)。

它的步骤如下:

设序列是stone[],从左往右,找一个满足stone[k-1] <= stone[k+1]的k,找到后合并stone[k]和stone[k-1],再从当前位置开始向左找最大的j,使其满足stone[j] > stone[k]+stone[k-1],插到j的后面就行。一直重复,直到只剩下一堆石子就可以了。在这个过程中,可以假设stone[-1]和stone[n]是正无穷的。

举个例子:

186 64 35 32 103

因为35<103,所以最小的k是3,我们先把35和32删除,得到他们的和67,并向前寻找一个第一个超过67的数,把67插入到他后面,得到:186 67 64 103,现在由5个数变为4个数了,继续:186 131 103,现在k=2(别忘了,设A[-1]和A[n]等于正无穷大)234 186,最后得到420。最后的答案呢?就是各次合并的重量之和,即420+234+131+67=852。

基本思想是通过树的最优性得到一个节点间深度的约束,之后证明操作一次之后的解可以和原来的解一一对应,并保证节点移动之后他所在的深度不会改变。具体实现这个算法需要一点技巧,精髓在于不停快速寻找最小的k,即维护一个“2-递减序列”朴素的实现的时间复杂度是O(n*n),但可以用一个平衡树来优化,使得最终复杂度为O(nlogn)。

GarsiaWachs算法优化+小细节优化:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 #include

2 #include

3 #include

4 #include

5 #include

6 #include

7 using namespacestd;8

9 const int N = 205;10 const int INF = 0x7fffffff;11

12 intstone[N];13 intn,t,ans;14

15 void combine(intk)16 {17 int tmp = stone[k] + stone[k-1];18 ans +=tmp;19 for(int i=k;i= 2 && stone[j] >= stone[j-2])27 {28 int d = t -j;29 combine(j-1);30 j = t -d;31 }32 }33

34 intmain()35 {36 //freopen("D:\\input.in","r",stdin);

37 while(~scanf("%d",&n))38 {39 for(int i=1;i<=n;i++)40 scanf("%d",stone+i);41 stone[0]=INF;42 stone[n+1]=INF-1;43 t = 3;44 ans = 0;45 for(int i=3;i<=n+1;i++)46 {47 stone[t++] =stone[i];48 while(stone[t-3] <= stone[t-1])49 combine(t-2);50 }51 while(t > 3) combine(t-1);52 printf("%d\n",ans);53 }54 return 0;55 }

0ms

小细节在于把数列前后加两个值INF和INF-1。这样就不需要每次判别上下界。至于-1,组合堆的最后一步里体现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值