bzoj4033: [HAOI2015]树上染色(树形dp)

4033: [HAOI2015]树上染色

Time Limit: 10 Sec  Memory Limit: 256 MB
Submit: 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

Sample Output

17
【样例解释】
将点1,2染黑就能获得最大收益。

HINT

 

2017.9.12新加数据一组 By GXZlegend

Source

鸣谢bhiaibogf提供

 

#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;
}
30暴力
/*
开始状态设的是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; 
}

 

posted @ 2018-10-10 17:56  安月冷  阅读(176)  评论(2编辑  收藏  举报