其实本来没有这一篇的,而且又是复习阶段,又没忍住瘾了一题...
题意:
树上给k个点染成黑色,n - k个点染成白色,求相同颜色两两距离之和的最大值
思路:
树上两两距离 -> 距离转路径,路径转边,只要计算每条边对答案的贡献
那么怎么计算边贡献?
对于一条边,只有在其两侧的相同结点才有贡献,否则不产生贡献(背包转化,选点)
那么自然得到每条边的贡献计算公式:
tot = k * (m - k) + (sz[v] - k) * (n - m - sz[v] + k)
(k:表示当前子树的黑色节点个数,m:表示总黑色结点个数)
转移:
dp[u][j] = max(dp[u][j], dp[u][j - k] + dp[v][k] + tot * w)
(经典01背包了属于是)
细节补充:
k必须正序枚举(在本题中未给定dp[u][0]时)
代码:
点击查看代码
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cctype>
#define ll long long
#define gc getchar
#define maxn 2005
using namespace std;
inline ll read(){
ll a=0;int f=0;char p=gc();
while(!isdigit(p)){f|=p=='-';p=gc();}
while(isdigit(p)){a=(a<<3)+(a<<1)+(p^48);p=gc();}
return f?-a:a;
}
struct ahaha{
int w,to,next;
}e[maxn<<1];int tot,head[maxn];
inline void add(int u,int v,int w){
e[tot].w=w,e[tot].to=v,e[tot].next=head[u];head[u]=tot++;
}
int n,m,sz[maxn];
ll f[maxn][maxn];
void dfs(int u,int fa){
sz[u]=1;f[u][0]=f[u][1]=0;
for(int i=head[u];~i;i=e[i].next){
int v=e[i].to;if(v==fa)continue;
dfs(v,u);sz[u]+=sz[v];
for(int j=min(m,sz[u]);j>=0;--j){ //此处倒序枚举是为了避免重复选取
if(f[u][j]!=-1) //在DP前应先加上当前子节点的子树纯白色的情况,这是下面也倒序枚举的前提
f[u][j]+=f[v][0]+(ll)sz[v]*(n-m-sz[v])*e[i].w;
for(int k=min(j,sz[v]);k;--k){
if(f[u][j-k]==-1)continue;
ll val=(ll)(k*(m-k)+(sz[v]-k)*(n-m-sz[v]+k))*e[i].w; //当前情况下连接子节点的边的贡献
f[u][j]=max(f[u][j],f[u][j-k]+f[v][k]+val);
}
}
}
}
int main(){memset(head,-1,sizeof head);
n=read();m=read();
if(n-m<m)m=n-m;
for(int i=1;i<n;++i){
int u=read(),v=read(),w=read();
add(u,v,w);add(v,u,w);
}memset(f,-1,sizeof f);
dfs(1,-1);
printf("%lld",f[1][m]);
return 0;
}

浙公网安备 33010602011771号