bzoj-1133 Kon

题意:

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



你可能感兴趣的:(poi,动态规划,bzoj,前缀和)