Vexoben
Jun 18, 2018
题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=4033
(刷新以获取数学公式)
题意
有一棵点数为的树,树边有边权。给你一个在~之内的正整数,你要在这棵树中选择个点,将其染成黑色,并将其他的个点染成白色。将所有点染色后,你会获得黑点两两之间的距离加上白点两两之间距离的和的收益。问收益最大值是多少。
题解
对于一条边,假设去掉它后分成的两个联通块中,黑色节点个数分别为,白色节点个数分别为,则定义它的贡献
定义状态表示以为根的子树,有个点被染成黑色,子树内的边产生的最大贡献和。
转移的时候,将节点的子树一个个加入。设是当前转移到的儿子,转移方程是:
其中是边产生的贡献,它的值为:
这样复杂度看起来是的。但是我们分析一下会发现复杂度为:
对于点对,仅在的时候对答案有贡献,从而复杂度是的。
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N=2050;
int n,m,E;
int fa[N],siz[N],fir[N],nex[N<<1],arr[N<<1];
LL g[N],len[N<<1],f[N][N];
inline void Add_Edge(int x,int y,LL l) {
nex[++E]=fir[x];
fir[x]=E; arr[E]=y; len[E]=l;
}
void dfs(int x) {
siz[x]=1;
for (int i=fir[x];i;i=nex[i]) {
if (arr[i]==fa[x]) continue;
fa[arr[i]]=x; dfs(arr[i]);
int sx=min(m,siz[x]);
int sy=min(m,siz[arr[i]]);
memset(g,0,sizeof(LL)*(m+1));
for (LL j=0;j<=sx;j++)
for (LL k=0;k<=sy;k++) {
if (j+k>m) break;
LL eval=len[i]*(k*(m-k)+(siz[arr[i]]-k)*(n-m-siz[arr[i]]+k));
g[j+k]=max(g[j+k],f[x][j]+f[arr[i]][k]+eval);
}
for (int j=0;j<=m;j++) f[x][j]=g[j];
siz[x]+=siz[arr[i]];
}
}
int main() {
scanf("%d%d",&n,&m);
for (int i=1;i<n;i++) {
int x,y; LL l; scanf("%d%d%lld",&x,&y,&l);
Add_Edge(x,y,l); Add_Edge(y,x,l);
}
dfs(1);
printf("%lld",f[1][m]);
return 0;
}