题目链接:bzoj4033
题目大意:
有一棵点数为N的树,树边有边权。给你一个在0~N之内的正整数K,你要在这棵树中选择K个点,将其染成黑色,并将其他的N-K个点染成白色。将所有点染色后,你会获得黑点两两之间的距离加上白点两两之间距离的和的收益。问收益最大值是多少。
题解:
treedp
可能很好想。。?但是我自己想的转移很迷啊(为了不暴露智商还是不要说了
说正解吧
f[i][j]就表示以i为根的子树中染了多少个黑点。–应该算挺常规的吧
我们可以发现,假设枚举了一条边i(x,y,c),那么它对收益的贡献就是c*(x那边黑点的个数*y那边黑点的个数+x那边白点的个数*y那边白点的个数)。于是状态转移就出来了。
假设在做以x为根的子树,枚举到了它的儿子y,然后就枚举x子树中黑点的个数i和y子树中黑点的个数j(注意:此时y还没归到x中,即这是个独立的枚举,x中的黑点只包含已经跑过了的儿子的)。那么这条边的贡献就有c*[j*(K-j)+(size[y]-j)*( n-K-(size[y]-j) )]。就是以x->y这条边来分界嘛。
然后就没什么好讲了吧。。
神tm漏了一个等号调了我一年。。
zz的我忘了家里存的是男神号的帐密直接交了。A了就算了我还WA了一发qwq。虽然很快改A了(抱着不让这个号留下以WA结尾的心理,A掉之后又上那个号交了一遍= =)。但是当我看到这个号之前可能是不同的人交了三次的时候,我觉得好愧疚(?)..希望他没有发现orz
对了,让我WA的罪魁祸首就是longlong,题目对于边权的大小什么也没说!我之前还特地看了一遍来着(如果是我眼瞎当我没说qwq)!于是我就直接一秒替换了。
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
#define maxn 2010
struct node
{
LL x,y,c,next;
}a[maxn*2];LL len,first[maxn];
LL n,K,size[maxn],f[maxn][maxn],ls[maxn];
LL mymin(LL x,LL y){return (x<y)?x:y;}
LL mymax(LL x,LL y){return (x>y)?x:y;}
void ins(LL x,LL y,LL c)
{
len++;a[len].x=x;a[len].y=y;
a[len].c=c;a[len].next=first[x];first[x]=len;
}
void treedp(LL x,LL fa)
{
LL i,j,k;size[x]=1;
for (k=first[x];k!=-1;k=a[k].next)
{
LL y=a[k].y;
if (y==fa) continue;
treedp(y,x);
for (i=0;i<=K;i++) ls[i]=0;
LL sizx=mymin(K,size[x]);
LL sizy=mymin(K,size[y]);
for (i=0;i<=sizx;i++)
for (j=0;j<=sizy;j++) if (i+j<=K)
{
LL tt=j*(K-j)+(size[y]-j)*(n-K-(size[y]-j));
ls[i+j]=mymax(ls[i+j],f[x][i]+f[y][j]+tt*a[k].c);
}
for (i=0;i<=K;i++) f[x][i]=ls[i];
size[x]+=size[y];
}
}
int main()
{
LL i,x,y,c;
scanf("%lld%lld",&n,&K);
len=0;memset(first,-1,sizeof(first));
for (i=1;i<n;i++)
{
scanf("%lld%lld%lld",&x,&y,&c);
ins(x,y,c);ins(y,x,c);
}
memset(f,0,sizeof(f));
treedp(1,0);
printf("%lld\n",f[1][K]);
return 0;
}