我们来看这样一道题目
有n堆石子排成一列,每堆石子有一个重量w[i],
每次合并可以合并相邻的两堆石子,一次合并的代价为两堆石子的重量和w[i]+w[i+1]。问安排怎样的合并顺序,能够使得总合并代价达到最小。
n<=100,w<=100
显然这是一道普及水平的区间DP
fi..j
表示将[i,j]合并成一堆所需要的最小代价
ai表示每堆石子的数量
sumi=∑ij=1ai
fi..j=fi..k+fk+1..j+sumj−sumi−1
正确做法是
O(n3)
#include<cstdio>
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int w[101],n,f[102][102];
main()
{
int x;
scanf("%d",&n);
for (int i=1;i<=n;i++)
scanf("%d",w+i),
w[i]+=w[i-1];
memset(f,63,sizeof(f));
for (int i=1;i<=n;i++) f[i][i]=0;
for (int i=n-1;i>=1;i--)
for (int j=i+1;j<=n;j++)
for (int k=i;k<=j-1;k++)
f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+w[j]-w[i-1]);
printf("%d",f[1][n]);
}
我们注意到,这是石子堆排列成一条线的
那如果这些石子堆是排成一个圆呢?
我们可以这么想
每两个相邻的石子堆之间连一条线段,那样每个石子堆都往外发出两条线段,每次合并(i,j)实际上是砍掉i
到j的线段,原先i到i的前一个石子堆的线段变成了j到i的前一个石子堆的线段
最初是n条线段,合并n-1次,最后肯定有一个线段用不到
直线排列时这条用不到的线段是已知的
但圆排列时是未知的
那我们就尝试枚举每一条所谓的线段
把这些石子堆当成直线排列,然后后面再加上一条排列相同的石子堆!
最终答案就是
minfi..i+n−1
i=1..n
复杂度仍是
O(n3)
#include<cstdio>
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int w[201],n,f[202][202];
main()
{
int x;
scanf("%d",&n);
for (int i=1;i<=n;i++)
scanf("%d",w+i),
w[i+n]=w[i];
for (int i=1;i<=n<<1;++i) w[i]+=w[i-1];
memset(f,63,sizeof(f));
for (int i=1;i<=n<<1;i++) f[i][i]=0;
for (int i=n<<1;i>=1;i--)
for (int j=i+1;j<=n<<1;j++)
for (int k=i;k<=j-1;k++)
f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+w[j]-w[i-1]);
f[0][0]=1<<30;
for(int i=1;i<=n;++i)
f[0][0]=min(f[0][0],f[i][i+n-1]);
printf("%d\n",f[0][0]);
}
求最大值类似,不再赘述
那我们变一下n的范围
n<=3000
那我们需要
O(n2)
做法
怎么办?
考虑四边形不等式优化
大神比我讲的更清楚
1、区间包含的单调性:如果对于 i≤i’ <j≤j’,有 w(i’,j)≤w(i,j’),那么说明w具有区间包含的单调性。(可以形象理解为如果小区间包含于大区间中,那么小区间的w值不超过大区间的w值)
2、四边形不等式:如果对于 i≤i’<j≤j’,有 w(i,j)+w(i’,j’)≤w(i’,j)+w(i,j’),我们称函数w满足四边形不等式。(可以形象理解为两个交错区间的w的和不超过小区间与大区间的w的和)
下面给出两个定理:
1、如果上述的 w 函数同时满足区间包含单调性和四边形不等式性质,那么函数 m 也满足四边形不等式性质
我们再定义 s(i,j) 表示 m(i,j) 取得最优值时对应的下标(即 i≤k≤j 时,k 处的 w 值最大,则 s(i,j)=k)。此时有如下定理
2、假如 m(i,j) 满足四边形不等式,那么 s(i,j) 单调,即 s(i,j)≤s(i,j+1)≤s(i+1,j+1)。
总之我们就可以优化成
O(n2)
的了
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
int n,w[3005];
int f[3005][3005],s[3005][3005];
main()
{
scanf("%d",&n);
memset(f,63,sizeof(f));
for (int i=1;i<=n;++i)
scanf("%d",w+i),
f[i][i]=0,s[i][i]=i,
w[i]+=w[i-1];
for (int i=n;i>=1;--i)
for (int j=i+1;j<=n;++j)
for (int k=s[i][j-1];k<=s[i+1][j];++k)
if (f[i][j]>f[i][k]+f[k+1][j]+w[j]-w[i-1])
f[i][j]=f[i][k]+f[k+1][j]+w[j]-w[i-1],
s[i][j]=k;
printf("%d\n",f[1][n]);
}
环形类似
那求最大值呢?
这好像不太符合四边形不等式的优化
但仍然可以
O(n2)
以cogs上石子归并加强版的环形为例
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
int n,w[4005];
int f[4005][4005],s[4005][4005];
main()
{
freopen("stone3.in","r",stdin);
freopen("stone3.out","w",stdout);
scanf("%d",&n);
for (int i=1;i<=n;++i)
scanf("%d",w+i),
s[i+n][i+n]=i+n,s[i][i]=i,w[n+i]=w[i];
for (int i=1;i<=n*2;++i) w[i]+=w[i-1];
for (int i=n*2-1;i>=1;--i)
for (int j=i+1;j<=min(n*2-1,i+n-1);++j)
f[i][j]=max(f[i][i]+f[i+1][j],f[j][j]+f[i][j-1])+w[j]-w[i-1];
for(int i=1;i<=n;++i) f[0][0]=max(f[0][0],f[i][i+n-1]);
printf("%d\n",f[0][0]);
}
注意这是严格
O(n2)
那我们来个终极版
n<=50000,求最小值
怎么办?
考虑GarsiaWachs算法
ACDreamer
#include<cstdio>
#define M 50001
using namespace std;
int n,a[M],tot,ans;
int in()
{
int t=0;char ch=getchar();
while (ch>'9'||ch<'0') ch=getchar();
while (ch>='0'&&ch<='9') t=(t<<1)+(t<<3)+ch-48,ch=getchar();
return t;
}
void unions(int x)
{
int tmp=a[x]+a[x-1];
ans+=tmp;
for (int i=x;i<tot;++i) a[i]=a[i+1];
int j;
for (j=x-1;a[j-1]<tmp&&j>1;--j) a[j]=a[j-1];
a[j]=tmp;--tot;
for (int d=tot-j;j>2&&a[j-2]<=a[j];d=tot-j)
unions(j-1),
j=tot-d;
}
main()
{
n=in();
for (int i=1;i<=n;++i) a[i]=in();
for (int i=1;i<=n;++i)
{
a[++tot]=a[i];
while (tot>2&&a[tot-2]<=a[tot]) unions(tot-1);
}
for (;tot>1;) unions(tot);
printf("%d\n",ans);
}
据说这个是
O(n2)
的,但是在一般数据下它可以到达
O(nlogn)
这本来是应该平衡树做的
写得很匆忙而且水平很渣
找个时间再来仔细研究……