题意:
给出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;
}