您的位置:首页 > 其它

石子合并问题总结

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(nlog⁡n)。

空间复杂度: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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: