洛谷 P3177 树上染色 解题报告
P3177 [HAOI2015]树上染色
题目描述
有一棵点数为\(N\)的树,树边有边权。给你一个在\(0\) ~ \(N\)之内的正整数\(K\),你要在这棵树中选择\(K\)个点,将其染成黑色,并将其他的\(N-K\)个点染成白色 。 将所有点染色后,你会获得黑点两两之间的距离加上白点两两之间的距离的和的受益。问受益最大值是多少。
输入输出格式
输入格式:
第一行包含两个整数 \(N, K\) 。接下来 \(N-1\) 行每行三个正整数 \(fr, to, dis\) , 表示该树中存在一条长度为\(dis\)的边\((fr,to)\) 。输入保证所有点之间是联通的。
输出格式:
输出一个正整数,表示收益的最大值。
说明
对于\(100%\)的数据,\(0<=K<=N <=2000\)
这里阐述一下我的思考方式。
首先,真的一下子想到贪心去了。前几天做了消防,觉得在树的直径上会有一些奇妙优美的性质,遂开始想办法证明。
然后考试完了还在证贪心\(emmmm\)。
解法很容易懂,但是很难想(我太蒻了)
正解是树形\(DP\)
令\(dp[i][j]\)为以\(i\)为根节点的子树中 在染了\(j\)个黑点时 所有的边再加上\(i\)头上的那个边 对整颗树 的答案贡献
有灵性就有灵性在这个整颗树上
在已经处理好子树\(v\)时,我们加入边\(E_k(u,v)\)(\(u\)为\(v\)的父亲)
则\(E_k\)的贡献即为边权\(w*\)子树\(u\)中白点个数子树\(u\)外面的数白点个数\(+w*\)子树\(u\)中黑点个数子树\(u\)外面的数黑点个数,这点不难想明白
当然,不做头顶上的边也是可以的,但很多树形\(dp\)管一下自己的脑门是比较方便的
转移时,我们对每棵子树做树上分组背包,最后在判断一下她自己头上的边
code
#include <cstdio>
#include <cstring>
#define ll long long
ll max(ll x,ll y) {return x>y?x:y;}
ll min(ll x,ll y) {return x<y?x:y;}
const int N=2001;
ll dp[N][N];
//根节点为i的子树j个点染黑所有边的最大答案
ll n,k;
struct Edge
{
ll to,next,w;
}edge[N*2];
ll head[N],cnt=0;
void add(ll u,ll v,ll w)
{
edge[++cnt].to=v;
edge[cnt].next=head[u];
edge[cnt].w=w;
head[u]=cnt;
}
int used[N];
ll dfs(ll u,ll c)
{
used[u]=1;
ll size=0;
memset(dp[u],-1,sizeof(dp[u]));
dp[u][0]=0;
for(ll i=head[u];i;i=edge[i].next)
{
ll v=edge[i].to,w=edge[i].w;
if(!used[v])
{
ll si=dfs(v,w);size+=si;
for(ll j=min(k,size);j>=0;j--)
{
ll r=min(j,min(si,k));
for(ll k0=0;k0<=r;k0++)
if(dp[u][j-k0]!=-1)
dp[u][j]=max(dp[u][j],dp[u][j-k0]+dp[v][k0]);
}
}
}
size++;
ll l=max(size+k-n,1);
for(ll i=min(k,size);i>=l;i--)
{
ll cc=c*(k-i)*i+c*(n-size-k+i)*(size-i);
dp[u][i]=max(dp[u][i],dp[u][i-1])+cc;
}
l=size+k-n;
dp[u][0]+=c*(n-size-k)*size;
return size;
}
int main()
{
scanf("%lld%lld",&n,&k);
ll u,v,w;
for(ll i=1;i<n;i++)
{
scanf("%lld%lld%lld",&u,&v,&w);
add(u,v,w),add(v,u,w);
}
dfs(1,0);
printf("%lld\n",dp[1][k]);
return 0;
}
细节问题:
1.分组背包转移方程为:\(dp[u][j]=max(dp[u][j],dp[u][j-k0]+dp[v][k0])\)
我们一定要判断\(dp[u][j-k0]\)的合法性
2.开\(long long\)不要吝啬空间(除非卡的太死,反正也就两倍),能看尽量就开,只开一部分在计算时真的不注意就可能丢失了或者爆了
3.背包的上下界,最好加上,不然常数一大可能会\(t\)。有的不加甚至会出现一些问题。
2018.5.15