[HAOI2015]树上染色
题目描述
有一棵点数为 n 的树,树边有边权。给你一个在 0∼n之内的正整数 kkk ,你要在这棵树中选择 k 个点,将其染成黑色,并将其他 的 n−k个点染成白色。将所有点染色后,你会获得黑点两两之间的距离加上白点两两之间的距离的和的受益。问受益最大值是多少。
输入格式
第一行包含两个整数 n,k。
第二到 n 行每行三个正整数 fr,to,dis,表示该树中存在一条长度为 dis 的边 (fr,to)。输入保证所有点之间是联通的。
输出格式
输出一个正整数,表示收益的最大值。
今日在洛谷无意中刷到的题目,一开始设置dp[i][j]表示i子树内有j个黑色点的最大价值,后来就发现转移不了,于是看了大神的题解,有所启发,受益匪浅。
对于这种树上选点计算点对贡献,往往不用考虑点的具体位置,只需要考虑边的贡献,因为边的贡献只需要知道左右两侧对应颜色的数量和边的权值即可计算。
设置dp[i][j]表示i子树内有j个黑色点所带来的贡献,考虑一个i的一个子树y有j’个黑色点,这条边的贡献为 w * 边两侧的黑色点乘积+w * 边两侧的白色点乘积。树形背包转移即可。
#include<bits/stdc++.h>
#define ll long long
#define rint register int
using namespace std;
const int N=2005;
inline int read()
{
int s=0,f=1; char ch=getchar();
while(ch<'0'||ch>'9') ch=getchar(),f=(ch=='-'?-1:f);
while(ch>='0'&&ch<='9') s=(s<<1)+(s<<3)+ch-'0',ch=getchar();
return s*f;
}
int tot=0,first[N],n,k;
struct fuk
{
int x,next,v;
}a[N<<1];
inline void add(int x,int to,int v)
{
tot++;
a[tot].x=to; a[tot].next=first[x]; first[x]=tot; a[tot].v=v;
}
ll dp[N][N];//子树i里面选了j个黑点的贡献
int sz[N];
inline void dfs(int x,int fa)
{
sz[x]=1;
memset(dp[x],-1,sizeof(dp[x]));
dp[x][0]=dp[x][1]=0;
for(int i=first[x];i;i=a[i].next)
{
int y=a[i].x;
if(y==fa) continue;
dfs(y,x);
sz[x]+=sz[y];
}
for(int i=first[x];i;i=a[i].next)
{
int y=a[i].x;
if(y==fa) continue;
for(int t=min(k,sz[x]);t>=0;t--)
for(int j=0;j<=min(t,sz[y]);j++)
{
if(dp[x][t-j]<0) continue;
ll val=(ll)j*(k-j)*a[i].v+(ll)(sz[y]-j)*(n-k+j-sz[y])*a[i].v;
dp[x][t]=max(dp[x][t],dp[x][t-j]+dp[y][j]+val);
}
}
}
int main()
{
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
n=read(),k=read();
for(int i=1;i<n;i++)
{
int x=read(),y=read(),z=read();
add(x,y,z); add(y,x,z);
}
dfs(1,0);
printf("%lld\n",dp[1][k]);
return 0;
}
启示:树上选点问题往往不用考虑点的具体位置,考虑边的贡献使得树形dp的转移方便。以前模拟赛做过很多类似思想的题目,算是很不错的题目。