石子合并
Posted NINGLONG
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了石子合并相关的知识,希望对你有一定的参考价值。
石子合并
有N堆石子排成一排,每堆石子有一定的数量。现要将N堆石子并成为一堆。合并的过程只能每次将相邻的两堆石子堆成一堆,每次合并花费的代价为这两堆石子的和,经过N-1次合并后成为一堆。求出总的代价最小值。
石子合并是一道十分经典的问题。注意到石子的合并可以当做区间的合并,显而易见的,我们有如下状态转移方程:
程序也十分简单:
#include<bits/stdc++.h> using namespace std; #define MAXN 220 const int INF=0x3f3f3f3f; int n,w[MAXN],sum[MAXN],dp[MAXN][MAXN]; int main(){ int n; scanf("%d",&n); for(int i=1;i<=n;i++){ scanf("%d",&w[i]); sum[i]=sum[i-1]+w[i]; } for(int i=n;i>=1;i--){ for(int j=i+1;j<=n;j++){ int tmp=INF; for(int k=i;k<j;k++) tmp=min(tmp,dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]); dp[i][j]=tmp; } } printf("%d\n",dp[1][n]); return 0; }
以上的算法正确性是可以保证的,但时间复杂度达到了O(n3),当n≥1000时,这样的时间复杂度是无法接受的。
考虑进一步优化。
回顾它的转移方程:
根据平行四边形不等式,我们可以从dp[i][j-1]的决策点到dp[i+1][j]的决策点枚举,时间复杂度是O(n2)
代码如下:
#include<bits/stdc++.h> using namespace std; #define MAXN 5000+10 typedef long long LL; const LL INF=0xffffff; int n,g[MAXN][MAXN]; LL a[MAXN],sum[MAXN],f[MAXN][MAXN]; int main(){ scanf("%d",&n); for(int i=1;i<=n;i++)scanf("%lld",&a[i]); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) f[i][j]=INF; for(int i=1;i<=n;i++){ sum[i]=sum[i-1]+a[i]; f[i][i]=0; g[i][i+1]=i; } for(int i=1;i<=n;i++)f[i][i+1]=sum[i+1]-sum[i-1]; for(int i=n-2;i;i--) for(int j=i+2;j<=n;j++) for(int k=g[i][j-1];k<=g[i+1][j];k++){ int tmp=f[i][k]+f[k+1][j]+sum[j]-sum[i-1]; if(f[i][j]>tmp){ f[i][j]=tmp; g[i][j]=k; } } printf("%lld",f[1][n]); return 0; }
尽管有了进一步的优化,但对于更大的数据,此算法也无能为力。(Codevs 2298)
实际上对于石子归并问题还有另一种算法:GarsiaWachs算法
算法的大致流程如下:
1.设一个序列是A[0..n-1],每次寻找最小的一个满足A[k-1]<=A[k+1]的k,把A[k]与A[k-1]合并
2.之后找最大的一个满足A[j]>A[k]+A[k-1]的j,把合并后的值A[k]+A[k-1]插入A[j]的后面。
由于此算法在其他题目中较少用到,在此不多赘述。可以证明,它的时间复杂度是O(nlogn)的
代码如下:
#include<bits/stdc++.h> using namespace std; #define MAXN 50000+10 typedef long long LL; int t,n,a[MAXN]; LL ans=0; void work(int k){ int tmp=a[k-1]+a[k]; ans+=tmp; for(int i=k;i<t-1;i++)a[i]=a[i+1]; t--; int j=0; for(j=k-1;j>0&&a[j-1]<tmp;j--)a[j]=a[j-1]; a[j]=tmp; while(j>=2&&a[j]>=a[j-2]){ int d=t-j; work(j-1); j=t-d; } } int main(){ scanf("%d",&n); ans=0; for(int i=0;i<n;i++)scanf("%d",&a[i]); t=1; for(int i=1;i<n;i++){ a[t++]=a[i]; while (t>=3&&a[t-3]<=a[t-1]) work(t-2); } while(t>1)work(t-1); printf("%lld\n",ans); return 0; }
对于石子合并问题我只给出以上三种,孰优孰劣,大家见仁见智。
以上是关于石子合并的主要内容,如果未能解决你的问题,请参考以下文章