bzoj4033: [HAOI2015]树上染色(树形dp)
4033: [HAOI2015]树上染色
Time Limit: 10 Sec Memory Limit: 256 MBSubmit: 3269 Solved: 1413
[Submit][Status][Discuss]
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
1 2 3
1 5 1
2 3 1
2 4 2
Sample Output
17
【样例解释】
将点1,2染黑就能获得最大收益。
【样例解释】
将点1,2染黑就能获得最大收益。
HINT
2017.9.12新加数据一组 By GXZlegend
Source
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 2001
using namespace std;
int n,m,k,ans,cnt;
int dis[N][N],e[N][N];
int p[N];
void calc_dis()
{
for(int k=1;k<=n;k++) for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
if(i!=j && j!=k)
e[i][j]=min(e[i][j],e[i][k]+e[k][j]);
}
}
void calc_ans()
{
int tmp=0;
for(int i=1;i<=n;i++) for(int j=i+1;j<=n;j++)
if(p[i]==p[j]) tmp+=e[i][j];
ans=max(ans,tmp);
}
void dfs(int now,int c)
{
if(now==n+1)
{
if(c==k)calc_ans();
return;
}
p[now]=0;dfs(now+1,c+1);
p[now]=1;dfs(now+1,c);
}
int main()
{
int x,y,z;
scanf("%d%d",&n,&k);
memset(e,127/3,sizeof e);
for(int i=1;i<=n;i++) e[i][i]=0;
for(int i=1;i<n;i++)
{
scanf("%d%d%d",&x,&y,&z);
e[x][y]=e[y][x]=z;
}
calc_dis();
dfs(1,0);
printf("%d\n",ans);
return 0;
}
/*
开始状态设的是f[i][j][0/1]。
表示i为根的子树染j个黑点,i节点为黑/白的最大值。
发现当i与儿子异色时没法转移,需要知道子树内点的具体染色情况。
所以说这个状态貌似不大行。
网上说“像这种题目其实是一个套路。在树上这种“两两之间”计算贡献和的问题,都拆开看每条边的贡献。”
我就很伤心,那不就说明我被套路了吗...
改一下状态f[i][j]表示i为根染j个黑点对总答案的最大贡献。
这个贡献的定义是啥呢?就是子树内所有边的贡献。
对于这条边
子树下有j个黑点,那么在其他位置就有k - j个黑点,所以由于黑点这条边被走过j*(k-j)次。
子树下有size[x] - j个白点,其他位置有n - k - (size[x]-j)个白点
所以由于白点这条边被走过(size[x]-j)*(n-k-size[x]+j)次。
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 2001
#define ll long long
using namespace std;
int n,k,ans,cnt,S,T;
int head[N],siz[N];
ll f[N][N],tmp;
struct edge{
int u,v,net;
ll w;
}e[N<<1];
inline int read()
{
int x=0,f=1;char c=getchar();
while(c>'9'||c<'0'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
return x*f;
}
inline void add(int u,int v,ll w)
{
e[++cnt].v=v;e[cnt].w=w;e[cnt].net=head[u];head[u]=cnt;
}
ll calc(ll val,int num,int x)//计算这条边的贡献
{
val=val*x*(k-x)+val*(num-x)*(n-k-(num-x));
return val;
}
void dfs(int u)
{
siz[u]=1;
for(int i=head[u];i;i=e[i].net)
{
int v=e[i].v;
if(siz[v]) continue;
dfs(v);
for(int x=siz[u];x>=0;x--) for(int y=siz[v];y>=0;y--)//类似树上背包,枚举当前子树染黑个数
{
tmp=f[u][x]+f[v][y]+calc(e[i].w,siz[v],y);
//f[u][x]其他子树内的点各自独立于当前子树内的点的贡献
//f[v][y]当前子树内的点各自独立于其他子树内的点的贡献
f[u][x+y]=max(f[u][x+y],tmp);
}
siz[u]+=siz[v];
}
}
int main()
{
int x,y,z;
n=read();k=read();
for(int i=1;i<n;i++)
{
x=read();y=read();cin>>z;
add(x,y,z);add(y,x,z);
}
dfs(1);printf("%lld\n",f[1][k]);
return 0;
}
折花枝,恨花枝,准拟花开人共卮,开时人去时。
怕相思,已相思,轮到相思没处辞,眉间露一丝。