4033: [HAOI2015]树上染色
Time Limit: 10 Sec Memory Limit: 256 MB
Submit: 1886 Solved: 805
Description
有一棵点数为N的树,树边有边权。给你一个在0~N之内的正整数K,你要在这棵树中选择K个点,将其染成黑色,并
将其他的N-K个点染成白色。将所有点染色后,你会获得黑点两两之间的距离加上白点两两之间距离的和的收益。
问收益最大值是多少。
Input
第一行两个整数N,K。
接下来N-1行每行三个正整数fr,to,dis,表示该树中存在一条长度为dis的边(fr,to)。
输入保证所有点之间是联通的。
N<=2000,0<=K<=N
Output
输出一个正整数,表示收益的最大值。
Sample Input
5 2
1 2 3
1 5 1
2 3 1
2 4 2
Sample Output
17
【样例解释】
将点1,2染黑就能获得最大收益。
题解:
这个题目伤透了我的心,注意一定要全开long long....
好了,这个题目和树形背包十分类似,状态十分显然,我们设dp[i][j] 表示以i为节点的黑点个数是j的最大价值,怎么转移呢?这里十分巧妙,我们直接算是不可能的,那样就成了暴力,所以我们可以考虑每条边对整个答案的贡献,因为是两两点对,所以是子树中白点的数量乘以不在子树中的白点数量加上黑点的,所以状态转移就是dp[now][j+have]=max(dp[now][j+have],dp[now][have]+dp[to][j]+quan*j*(k-j)+(ll)(quan*(size[to]-j)*(n-k-size[to]+j)));后面的两个表示变对白点的贡献和边对黑点的贡献,然后我们跑一边01背包就可以了。最后不要忘记将子的结果和i节点合并。
代码
#include<iostream> #include<stdio.h> #include<stdlib.h> #include<algorithm> #include<cstring> #define ll long long const int MAXN=3005; using namespace std; ll dp[MAXN][MAXN]; ll size[MAXN]; ll n,k,num=0; struct edge{ ll first; ll next; ll to; ll quan; }a[MAXN*2]; void addedge(ll from,ll to,ll quan){ a[++num].to=to; a[num].quan=quan; a[num].next=a[from].first; a[from].first=num; } void dfs(ll now,ll f){ size[now]=1; for(ll i=a[now].first;i;i=a[i].next){ ll to=a[i].to; if(to==f) continue; dfs(to,now); size[now]+=size[to]; } ll fill=1; for(ll i=a[now].first;i;i=a[i].next){ ll to=a[i].to,quan=a[i].quan; if(to==f) continue; for(ll have=fill;have>=0;have--){ for(ll j=min(size[to],k);j>=0;j--){ if(dp[to][j]>=0&&have+j<=k){ dp[now][j+have]=max(dp[now][j+have],dp[now][have]+dp[to][j]+quan*j*(k-j)+(ll)(quan*(size[to]-j)*(n-k-size[to]+j))); } } } fill=min(k,fill+size[to]); } } int main(){ scanf("%lld%lld",&n,&k); memset(dp,0,sizeof(dp)); memset(size,0,sizeof(size)); for(int i=1;i<n;i++){ ll x,y,z;scanf("%lld%lld%lld",&x,&y,&z); addedge(x,y,z); addedge(y,x,z); } dfs(1,0); printf("%lld\n",dp[1][k]); }
时间: 2024-10-18 06:08:24