石子合并问题总结
2018-02-22 17:15
351 查看
直线石子合并
题目链接时间复杂度:O(n3)O(n3)。
空间复杂度:O(n2)O(n2)。
∀k∈[l,r),dp[l][r]=mindp[l][k]+dp[k+1][r]+∑i=lra[i]∀k∈[l,r),dp[l][r]=mindp[l][k]+dp[k+1][r]+∑i=lra[i], 为合并区间[l,r][l,r]的最小合并代价。
代码:
#include<bits/stdc++.h> #define LL long long using namespace std; const int maxn = 1e3+7; int dp[maxn][maxn]; int sum[maxn]; int a[maxn]; int inf = 0x3f3f3f3f; int d(int l,int r){ if(dp[l][r] != inf) return dp[l][r]; for(int k = l;k<r;k++){ dp[l][r] = min(dp[l][r],d(l,k)+d(k+1,r)+sum[r]-sum[l-1]); } return dp[l][r]; } int main(){ ios::sync_with_stdio(0); int n;cin >> n; for(int i = 1;i<=n;i++){ cin >> a[i]; sum[i]+=a[i]+sum[i-1]; } memset(dp,0x3f,sizeof(dp)); for(int i = 0;i<=n;i++) dp[i][i] = 0; cout << d(1,n) << endl; }
圆形石子合并
<20000
span style="position: absolute; clip: rect(1.181em 1003.59em 2.359em -999.997em); top: -2.2em; left: 0em;">n∼ 102n∼ 102
题目链接
注意:此题是多组输入。
解法1
时间复杂度:O(n3)O(n3)。空间复杂度:O(n2)O(n2)。
dp方程为
∀k∈[l,r), dp[l][r]=dp[l][k]+∑i=lra[i], when l≤r∀k∈[l,r), dp[l][r]=dp[l][k]+∑i=lra[i], when l≤r以及
∀k∈[1,r)∪[l,n], dp[l][r]+∑i=lna[i]+∑i=1ra[i], when l>r∀k∈[1,r)∪[l,n], dp[l][r]+∑i=lna[i]+∑i=1ra[i], when l>r。
初始条件:
dp[i][j]=0,when i=j,dp[i][j]=∞,otherwise.dp[i][j]=0,when i=j,dp[i][j]=∞,otherwise.
递归
#include<bits/stdc++.h> #define LL long long using namespace std; const int maxn = 1e2+7; int dp[maxn][maxn]; int sum[maxn]; int a[maxn]; int inf = 0x3f3f3f3f; int n; int d(int l,int r){ if(l*r == 0) return inf; if(dp[l][r] < inf) return dp[l][r]; if(l<=r) for(int k = l;k<r;k++){ dp[l][r] = min(dp[l][r],d(l,k)+d(k+1,r)+sum[r]-sum[l-1]); } else{ for(int k = l;k<r || k>=l;k=k%n+1){ dp[l][r] = min(dp[l][r],d(l,k)+d((k)%(n)+1,r)+sum -sum[l-1]+sum[r]); } } return dp[l][r]; } int main(){ ios::sync_with_stdio(0); while(cin >> n){ memset(dp,0x3f,sizeof(dp)); memset(sum,0,sizeof(sum)); for(int i = 0;i<=n;i++) dp[i][i] = 0; for(int i = 1;i<=n;i++){ cin >> a[i]; sum[i]=a[i]+sum[i-1]; } if(n == 1) { cout << a[1] << endl; continue; } int ans = inf; for(int i = 1;i<=n;i++) ans = min(ans,d(i,(i+n-1)%n)); cout << ans << endl; } }
解法2
将石子堆复制一份,用直线情形解决。注意数组大小。
迭代
#include<bits/stdc++.h> #define LL long long using namespace std; const int maxn = 2e2+7; int dp[maxn][maxn], sum[maxn]; int n; int main(){ while(~scanf("%d",&n)){ memset(dp,0x1f,sizeof(dp)); for(int i = 0;i<=2*n;i++){ dp[i][i] = 0; } for(int i = 1;i<=n;i++){ scanf("%d",&sum[i]); sum[i+n] = sum[i]; } for(int i = 1;i<=2*n;i++){ sum[i]+=sum[i-1]; } for(int d = 1;d<n;d++){ for(int i = 1;i<=2*n-d;i++){ int j = i+d; for(int k = i;k<j;k++){ dp[i][j] = min(dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1],dp[i][j]); } } } int ans = 0x1f1f1f1f; for(int i = 1;i<=n;i++){ int j = i + n - 1; ans = min(ans,dp[i][j]); } printf("%d\n",ans); } }
递归
这里略去……因为差不多。四边形不等式优化
时间复杂度:O(n2)O(n2)。空间复杂度同
直线石子合并
迭代
鸽了环形石子合并
题目链接n∼103n∼103
#include<bits/stdc++.h> #define LL long long using namespace std; const int maxn = 2e3+7; int dp[maxn][maxn], sum[maxn], s[maxn][maxn]; int n; int main(){ scanf("%d",&n); memset(dp,0x1f,sizeof(dp)); for(int i = 0;i<=2*n;i++){ dp[i][i] = 0; s[i][i] = i; } for(int i = 1;i<=n;i++){ scanf("%d",&sum[i]); sum[i+n] = sum[i]; } for(int i = 1;i<=2*n;i++){ sum[i]+=sum[i-1]; } for(int d = 1;d<n;d++){ for(int i = 1;i<=2*n-d;i++){ int j = i+d; int &temp = dp[i][j]; int &ts = s[i][j]; for(int k = s[i][j-1];k<=s[i+1][j];k++){ int orz = dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]; if(temp > orz){ temp = orz; ts = k; } } } } int ans = 0x1f1f1f1f; for(int i = 1;i<=n;i++){ int j = i + n - 1; ans = min(ans,dp[i][j]); } printf("%d\n",ans); }
GarsiaWachs算法
题目链接n∼5×104n∼5×104
时间复杂度:O(n2)O(n2),若使用平衡树维护O(nlogn)O(nlogn)。
空间复杂度:O(n)O(n)。
因为这个算法不是那么直观,所以我多写一点。
从朴素贪心开始
考虑三块连续的石堆[a,b,c][a,b,c],计算两种合并方法的代价。s1s2=(a+b)+((a+b)+c)=2a+2b+c=(a+(b+c))+(b+c)=a+2b+2cs1=(a+b)+((a+b)+c)=2a+2b+cs2=(a+(b+c))+(b+c)=a+2b+2c
注意到,若a≤ca≤c,就有s1≤s2s1≤s2。所以,三个连续石堆的合并方法,只与aa和cc的大小关系有关。
所以我们就能得到一个朴素的贪心算法,即:
不断地从左往右遍历,找到形似[a,b,c][a,b,c]且a≤ca≤c的三个连续石堆,就合并aa和bb。
但是朴素贪心算法是错误的。
反例: [6,4,2,1,3,5,7][6,4,2,1,3,5,7]。最小代价是74,贪心结果是76。
形式证明鸽了。
Garisia-Wachs的改进
设石堆数组为stonestone,下标从0开始。其中每个元素代表此石堆的石子数量。约定stone[0]=stone[len]=+∞stone[0]=stone[len]=+∞, ans=0ans=0。
此数组长度记为|stone|=len|stone|=len,计入两端。也就是说[+∞,1,+∞][+∞,1,+∞]长度为3。同时,因为至少有一个石堆,3也是stonestone数组的最短可能长度。
数组的下标较小的一端为左端。
具体算法如下:
1. 依次令i=1…len−2i=1…len−2,直到有stone[i]≤stone[i+2]stone[i]≤stone[i+2]。(肯定会出现的,想想为啥)
2. 令temp=stone[i]+stone[i+1]temp=stone[i]+stone[i+1]。
3. 向左寻找,找到jj满足j<ij<i且stone[j]>tempstone[j]>temp。
4.删除stone[i+1]stone[i+1]这个元素(右方元素依次左移),并将temptemp插入到stone[j]stone[j]的右边(右方元素依次右移)。本次合并代价为temptemp,累加入ansans。
5.反复执行此过程,直到stonestone数组内只有一个非无穷元素(只剩一个石堆 ),此时ansans的值即是最小合并代价。
正确性证明……鸽了。
vector朴素实现
用stl的vector实现。用时接近2s。注意答案用long long。
#include<bits/stdc++.h> #define LL long long #define sz(v) int(v.size()) using namespace std; const int maxn = 5e4+7; const LL inf = 0x3f3f3f3f3f3f3f3f; int n,x; int main(){ scanf("%d",&n); vector<LL> v; v.push_back(inf); for(int i = 1;i<=n;i++){ scanf("%d",&x); v.push_back(x); } v.push_back(inf); LL ans = 0; for(int i = 1;i+2<sz(v);i++){ if(v[i]<=v[i+2]){ v[i] += v[i+1]; LL temp = v[i]; ans += temp; v.erase(v.begin()+i+1); //删除v[i+1]这个元素 for(int j = i;j>=1;j--){ if(v[j-1]>temp){ //往回找到合适的位置 v.erase(v.begin()+i);//删除v[i]这个元素 v.insert(v.begin()+j,temp);//插入v[i]到v[j]之后 break; } } i = 0; } } printf("%lld\n",ans); }
值得注意的是,vector的insert方法在传入的位置之前(左方)插入元素。
有点厉害的实现
265ms……很强51nod上扒的。
#include <stdio.h> #include <algorithm> #include <iostream> #include <string.h> #define MAXN 50005 #define LL long long using namespace std; int n, num; LL ans; int data[MAXN]; void dfs(int now) { int j; int temp = data[now - 1] + data[now];//代价 ans += (LL)temp; for(int i = now; i < num - 1; i++) data[i] = data[i + 1]; num--; for(j = now - 1; j > 0 && data[j - 1] < temp; j--) data[j] = data[j - 1]; data[j] = temp; while(j >= 2 && data[j - 2] <= data[j]) { int d = num - j; dfs(j - 1); j = num - d; } } int main() { scanf("%d", &n); for(int i = 0; i < n; i++) scanf("%d", &data[i]); num = 1; ans = 0; for(int i = 1; i < n; i++) { data[num++] = data[i]; while(num>=3 && data[num-3]<=data[num-1]) dfs(num - 2); } while(num > 1) dfs(num - 1); printf("%lld\n", ans); return 0; }