直线石子合并
时间复杂度:
。
空间复杂度:
。
代码:
#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;
}
圆形石子合并
题目链接
注意:此题是多组输入。
解法1
时间复杂度:
。
空间复杂度:
。
dp方程为
初始条件:
递归
#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[n]-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);
}
}
递归
这里略去……因为差不多。
四边形不等式优化
时间复杂度:
。
空间复杂度同
直线石子合并
迭代
鸽了
环形石子合并
#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算法
时间复杂度:
,若使用平衡树维护
。
空间复杂度:
。
因为这个算法不是那么直观,所以我多写一点。
从朴素贪心开始
考虑三块连续的石堆
,计算两种合并方法的代价。
注意到,若 ,就有 。所以,三个连续石堆的合并方法,只与 和 的大小关系有关。
所以我们就能得到一个朴素的贪心算法,即:
不断地从左往右遍历,找到形似
且
的三个连续石堆,就合并
和
。
但是朴素贪心算法是错误的。
反例:
。最小代价是74,贪心结果是76。
形式证明鸽了。
Garisia-Wachs的改进
- 设石堆数组为 ,下标从0开始。其中每个元素代表此石堆的石子数量。
- 约定 , 。
- 此数组长度记为 ,计入两端。也就是说 长度为3。同时,因为至少有一个石堆,3也是 数组的最短可能长度。
- 数组的下标较小的一端为左端。
具体算法如下:
1. 依次令
,直到有
。(肯定会出现的,想想为啥)
2. 令
。
3. 向左寻找,找到
满足
且
。
4.删除
这个元素(右方元素依次左移),并将
插入到
的右边(右方元素依次右移)。本次合并代价为
,累加入
。
5.反复执行此过程,直到
数组内只有一个非无穷元素(只剩一个石堆 ),此时
的值即是最小合并代价。
正确性证明……鸽了。
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;
}