题目描述
有一棵点数为 N 的树,树边有边权。给你一个在 0~ N 之内的正整数 K ,你要在这棵树中选择 K个点,将其染成黑色,并将其他 的N-K个点染成白色 。 将所有点染色后,你会获得黑点两两之间的距离加上白点两两之间的距离的和的受益。问受益最大值是多少。
输入输出格式
输入格式:
第一行包含两个整数 N, K 。接下来 N-1 行每行三个正整数 fr, to, dis , 表示该树中存在一条长度为 dis 的边 (fr, to) 。输入保证所有点之间是联通的。
输出格式:
输出一个正整数,表示收益的最大值。
输入输出样例
输入样例#1:
3 1 1 2 1 1 3 2
输出样例#1:
3
说明
对于 100% 的数据, 0<=K<=N <=2000
题解:
最终的收益等于每一条边的收益(权值乘以被用到的次数)的和,假设dp[i][j]表示以i为根的子树内边的收益,从叶子到根的顺序计算每条边的收益,遍历到i点时,i的子树里的边已经被计算过了,i到其儿子的边被新加了进来,用这些新加的边的收益和子树的收益更新以i为根的子树的收益。
转移方程为dp[i][j]=dp[k][l]+边权*子树内黑点*子树外黑点+边权*子树内白点*子树外白点。
一开始我以为自己计算的是答案的两倍,这样智障了很久,后来把/2删掉后过了,我又仔细思考了一下,我是一条边一条边的算的,所以只会算一次。
#include<algorithm> #include<iostream> #include<cstdlib> #include<cstdio> #define nn 2010 #define mm 4010 #define lo long long #define inf -100000000 using namespace std; int e=0; int fir[nn],nxt[mm],to[mm],w[mm],size[nn],n,k; lo dp[nn][nn]; bool vis[nn]; int get() { int ans=0,f=1;char ch=getchar(); while(!isdigit(ch)) {if(ch==‘-‘) f=-1;ch=getchar();} while(isdigit(ch)) {ans=ans*10+ch-‘0‘;ch=getchar();} return ans*f; } void add(int a,int b,int c) { nxt[++e]=fir[a];fir[a]=e;to[e]=b;w[e]=c; nxt[++e]=fir[b];fir[b]=e;to[e]=a;w[e]=c; } void dfs(int o) { size[o]=1; for(int i=fir[o];i;i=nxt[i]) if(!vis[to[i]]) { vis[to[i]]=1; dfs(to[i]); size[o]+=size[to[i]]; } } void solve(int o) { dp[o][0]=0; dp[o][1]=0; for(int i=fir[o];i;i=nxt[i]) if(size[o]>size[to[i]]) { solve(to[i]); for(int j=size[o];j>=0;j--) //把size[o]写成了size[i] for(int p=0;p<=size[to[i]]&&p<=j;p++) dp[o][j]=max(dp[o][j],dp[to[i]][p]+dp[o][j-p]+(lo)p*(k-p)*w[i]+(lo)w[i]*(size[to[i]]-p)*(n-k-size[to[i]]+p)); } } int main() { n=get(),k=get(); int a,b,c; for(int i=1;i<n;i++) { a=get();b=get();c=get(); add(a,b,c); } vis[1]=1; dfs(1); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) dp[i][j]=inf; solve(1); printf("%lld",dp[1][k]); return 0; }
时间: 2024-10-05 04:56:28