题意:
给出n个数轴上的点,每两个点有一条带权的边;
现可以选择在n-1个区间中切k次,使切断的边权最大;
注意同一条边被切断多次只计算一次;
n<=600,k<=50,总权值<=2*10^9;
题解:
Poi~
我的思路就是做相反的问题,之后用总和去减就好了;
f[i][j]最后一次在i点切,切j次没被切到的最小边权;
这个状态显然就每条边只能计算一次了;
转移f[i][j]=min(f[k][j-1]+calc(k,i)) (1<=k<i);
calc(k,i)计算它们之间的总边权,用个前缀和数组就可以做到O(1)了;
状态n*k,转移n,总复杂度O(k*n^2);
其实这个做完了正着搞也就会了。。只是这样比较好想而已;
值得一提这题没SPJ。。但是最优方案似乎只有一组;
因为我们都不是按字典序输出的= =;
代码:
#include<stdio.h> #include<string.h> #include<algorithm> #define N 650 using namespace std; int to[N][N],sum[N][N]; int f[N][55],pre[N][55]; void add(int n) { for(int x=1;x<=n;x++) for(int y=1;y<=n;y++) sum[x][y]=sum[x][y-1]-sum[x-1][y-1]+sum[x-1][y]+to[x][y]; } int calc(int l,int r) { return sum[r][r]-sum[l][r]-sum[r][l]+sum[l][l]; } void out(int x,int k,bool flag) { if(!k) return ; out(pre[x][k],k-1,0); printf("%d%c",x,flag?'\n':' '); } int main() { int n,m,i,j,k,sum,temp,ans,p; scanf("%d%d",&n,&m); for(i=1,sum=0;i<n;i++) { for(j=i+1;j<=n;j++) { scanf("%d",&to[i][j]); sum+=to[i][j]; } } add(n); for(i=1,ans=0,p=0;i<=n;i++) { f[i][0]=0x7f7f7f7f; f[i][1]=calc(0,i); for(j=2;j<=m;j++) { f[i][j]=0x7f7f7f7f; for(k=1;k<i;k++) { if(f[k][j-1]!=0x7f7f7f7f&&f[i][j]>(temp=f[k][j-1]+calc(k,i))) { f[i][j]=temp; pre[i][j]=k; } } } if(sum-f[i][min(m,i)]-calc(n,i)>ans) { ans=sum-f[i][m]-calc(n,i); p=i; } } out(p,m,1); return 0; }