题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2829
题目大意:有一段铁路有n个站,每个站可以往其他站运送粮草,现在要炸掉m条路使得粮草补给最小,粮草补给的公式是将每个站能收到的粮草的总和。
4----5-----1-----2
粮草总和为4*5 + 4*1 + 4*2 + 5*1 + 5*2 + 1*2 = 49.
4----5 1-----2
粮草总和为4*5 + 1*2 = 22.
4 5-----1------2
粮草总和为5*1 + 5*2 + 1*2 = 17.
解题思路:参考,状态转移方程为dp[i][j]=min{dp[k][j-1]+cost[k+1][i]}(k<i),若cost[i][j]满足凸四边形不等式,则可以用四边形优化。
这里我们有一个定理:cost为凸当且仅当 cost[i][j] + cost[i+1][j+1] <= cost[i+1][j] + cost[i][j+1]。
我们可以把原式cost[i][j] + cost[i'][j'] <= cost[i][j'] + cost[i'][j]变为cost[i + 1][j + 1] - cost[i + 1][j] <= cost[i][j + 1] - cost[i][j],然后固定j变化i,看coat[i][j+1] - cost[i][j]是关于i递增还是递减,如果是递减,则cost为凸。
一般如果不能直接看出来,可以进行打表。此题cost[i][j]满足该定理,于是可以用四边形优化将复杂度降至O(n^2)。
注意:这里的是s[i][j]处理跟石子归并时不一样,还有i是倒着来的,不是正着的。
代码:
1 #include<iostream> 2 #include<cstring> 3 using namespace std; 4 typedef long long LL; 5 const int N=1e3+5; 6 const long long INF=0x3f3f3f3f; 7 LL sum[N],dp[N][N],cost[N][N],s[N][N];//s[i][j]记录dp[i][j]的最优切割点 8 9 int main(){ 10 int n,m; 11 while(~scanf("%d%d",&n,&m)){ 12 if(n==0&&m==0) 13 break; 14 memset(cost,0,sizeof(cost)); 15 memset(dp,INF,sizeof(dp)); 16 for(int i=1;i<=n;i++){ 17 scanf("%lld",&sum[i]); 18 sum[i]+=sum[i-1]; 19 s[i][0]=0; 20 } 21 //计算cost[i][j] 22 for(int i=1;i<=n;i++){ 23 for(int j=1;j<i;j++){ 24 cost[1][i]+=(sum[j]-sum[j-1])*(sum[i]-sum[j]); 25 } 26 dp[i][0]=cost[1][i]; 27 } 28 for(int k=1;k<=n;k++){ 29 for(int i=k+1;i<=n;i++){ 30 cost[k][i]=cost[1][i]-cost[1][k-1]-sum[k-1]*(sum[i]-sum[k-1]); 31 } 32 } 33 34 //j为轰炸次数,当i = 1或者j = n时为边界对s的处理就是为了处理这些边界 35 for(int j=1;j<=m;j++){ 36 s[n+1][j]=n-1; 37 for(int i=n;i>=j;i--){ 38 for(int k=s[i][j-1];k<=s[i+1][j];k++){ 39 LL tmp=dp[k][j-1]+cost[k+1][i]; 40 if(tmp<dp[i][j]){ 41 dp[i][j]=tmp; 42 s[i][j]=k; 43 } 44 } 45 } 46 } 47 printf("%lld ",dp[n][m]); 48 } 49 return 0; 50 }