hdu3506 Monkey Party (区间dp+四边形不等式优化)

时间:2023-01-04 13:16:59

题意:给n堆石子,每次合并相邻两堆,花费是这两堆的石子个数之和(1和n相邻),求全部合并,最小总花费

若不要求相邻,可以贪心地合并最小的两堆。然而要求相邻就有反例

为了方便,我们可以把n个数再复制一遍,放到第n个数后,就不用考虑环的问题了

我们设f[i][j]为合并区间[i,j]所需要的最小花费,然后就可以得到

f[i][j]=min{f[i][k]+f[k+1][j]+sum[i,j]} ,i<=k<=j,i<j;

f[i][i]=0

然后就可以用$O(n^3)$的复杂度递推啦。此题结束。

然而n<=1000...

四边形不等式:

若f[i][j]=min{f[i][k]+f[k+1][j]+w[i][j]} ,i<=k<=j;

 s[i][j]为使f[i][j]取到最小值的k ,其中有(a<=b<=c<=d)

  1.w[b][c]<=w[a][d] (w满足区间包含单调性)

  2.w[a][c]+w[b][d]<=w[b][c]+w[a][d] (w满足四边形不等式)

 则f也满足四边形不等式(*)

 所以s[i][j-1]<=s[i][j]<=s[i+1][j] (**)

*、**:太麻烦了不证了!

于是就可以优化刚才的dp(sum显然满足以上两点),每次的k不是从i枚举到j,而是从s[i][j-1]枚举到s[i+1][j],这样,平摊下来,就可以在O(1)复杂度完成f[i][j]的计算

然而我很沙雕的设f[i][j]表示长度为i,从j开始的区间了..虽然影响不大但是感觉写起来变得有点迷

然后按照我的写法,n=1的时候是要特判的...

 #include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#include<cmath>
#define LL long long int
#define inf 0x3f3f3f3f
using namespace std;
const int maxn=; LL rd(){
LL x=;char c=getchar();int neg=;
while(c<''||c>''){if(c=='-') neg=-;c=getchar();}
while(c>=''&&c<='') x=x*+c-'',c=getchar();
return x*neg;
} int N,num[maxn];
int f[maxn][maxn][],sum[maxn]; int main(){
int i,j,k;
while(~scanf("%d",&N)){
int ans=inf;
for(i=;i<=N;i++) num[i+N]=num[i]=rd();
if(N==){printf("0\n");continue;}
for(i=;i<=*N;i++) sum[i]=sum[i-]+num[i],f[][i][]=i;
for(i=;i<=N;i++){
for(j=;j<*N-i+;j++){
f[i][j][]=inf;
for(k=f[i-][j][];k<=f[i-][j+][];k++){
int a=f[k-j+][j][]+f[i-k+j-][k+][];
if(a<f[i][j][]) f[i][j][]=a,f[i][j][]=k;
}f[i][j][]+=sum[i+j-]-sum[j-];
if(i==N) ans=min(ans,f[i][j][]);
}
}printf("%d\n",ans);
} return ;
}