【HDU3506,HOJ2952】Monkey Party-环状区间合并DP+四边形不等式优化
测试地址:Monkey Party
题目大意:N只猴子坐成一圈,每只猴子有一个介绍时间,一开始每只猴子之间互不认识(除了自己认识自己),现在要对它们进行介绍使得它们之间互相认识,每一次介绍是发生在相邻的两只猴子之间的,每次介绍完后,两只猴子所认识的所有猴子都会互相认识,花费的时间为两只猴子所认识的所有猴子的介绍时间之和,求介绍所有猴子认识的最小时间。
做法:注意到认识关系绝对不会“跨越”,即相互认识的猴子一定在环上连续的一段上,这就启示我们想到区间合并型DP。设f(i,j)为介绍从第i到第j只猴子认识的最小时间,w(i,j)为第i到第j只猴子的总介绍时间,则状态转移方程为:f(i,j)=min{f(i,k-1)+f(k,j)+w(i,j)} (i<k≤j)。可是这一题是环状的,环上的区间合并要怎么做呢?我们可以看做先把环的某一处割开形成一条链,然后再在链上做区间合并,最后把这些结果相比对求出最小值即可。具体的做法是:在第n只猴子后面再加入n只猴子,其中新加入的第i只猴子的信息和原来的第i只猴子信息相同,这样的话新的数列上的区间[1,n],[2,n+1],...,[n,2*n-1]就一一对应了在原来环上切割所得到的链,所以答案就是min{f(i,i+n-1)} (1≤i≤n)。
然而又回到状态转移方程上来,我们发现暴力做这个方程的复杂度是O(N^4)的,而N达到1000,显然无法通过。我们发现实际上w(i,j)就是从i到j一段的和,这个我们可以用前缀和处理,即用一个数组sum存储从1到i一段的和,所以w(i,j)=sum[j]-sum[i-1],方程降了一维。然而还是不够,我们看这个状态转移方程的形式,感觉它能进行四边形不等式优化,实际上就是可以,我们只需要证明w(i,j)满足区间包含单调性和四边形不等式即可。w满足区间包含单调性由w的定义来看是显然的,要证明w满足四边形不等式即证明对于任意i≤i'<j≤j',w(i,j)+w(i',j')≤w(i,j')+w(i',j),我们把式子展开之后发现左右都是sum[j]+sum[j']-sum[i-1]-sum[i'-1],两边相等,定理得证。至于后面证明f满足四边形不等式看这里,里面所讲的第一个例子就是这个方程。于是原状态转移方程复杂度降到O(N^2),可以通过全部数据。
以下是本人代码:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
int n,sum[2010],s[2010][2010],f[2010][2010];
int getw(int i,int j)
{
return sum[j]-sum[i-1];
}
int main()
{
while(scanf("%d",&n)!=EOF)
{
sum[0]=sum[n]=0;
for(int i=1,a;i<=n;i++)
{
scanf("%d",&a);
sum[i]=sum[i-1]+a;
sum[i+n]=sum[i+n-1]+a;
}
for(int i=1;i<=n;i++)
sum[i+n]+=sum[n];
memset(f,0,sizeof(f));
for(int i=1;i<=2*n;i++) s[i][i]=i;
for(int l=2;l<=n;l++)
for(int i=1;i+l-1<=2*n;i++)
{
int j=i+l-1;
f[i][j]=1000000000;
for(int k=s[i][j-1];k<=s[i+1][j];k++)
if (i<k&&f[i][j]>=f[i][k-1]+f[k][j]+getw(i,j))
{
f[i][j]=f[i][k-1]+f[k][j]+getw(i,j);
s[i][j]=k;
}
}
int ans=1000000000;
for(int i=1;i<=n;i++)
ans=min(ans,f[i][i+n-1]);
printf("%d\n",ans);
}
return 0;
}