[CQOI2009] 叶子的染色 - 贪心或动规


题目描述

  给一棵m个结点的无根树,你可以选择一个度数大于1的结点作为根,然后给一些结点(根、内部结点和叶子均可)着以黑色或白色。你的着色方案应该保证根结点到每个叶子的简单路径上都至少包含一个有色结点(哪怕是这个叶子本身)。
  对于每个叶结点u,定义c[u]为从根结点到u的简单路径上最后一个有色结点的颜色。给出每个c[u]的值,设计着色方案,使得着色结点的个数尽量少。


输入格式

第一行包含两个正整数m, n,其中n是叶子的个数,m是结点总数。结点编号为1,2,…,m,其中编号1,2,… ,n是叶子。以下n行每行一个0或1的整数(0表示黑色,1表示白色),依次为c[1],c[2],…,c[n]。以下m-1行每行两个整数a,b(1<=a < b <= m),表示结点a和b 有边相连。


输出格式

仅一个数,即着色结点数的最小值。


样例数据

样例输入

5 3
0
1
0
1 4
2 5
4 5
3 5

样例输出

2


数据规模

这里写图片描述


题目分析

这题一看就是树形动规,但是今天考试的时候怕写错,写了个贪心,心里想骗个80分满足了,居然意外的AC了。
后来问了一下同学,似乎也没有找出反例,于是我伪证了一波,貌似这个贪心是对的。。。
贪心策略:
若某结点的儿子结点中需要染黑色的比白色多,就将此节点标记为要染黑色,将要染白色的儿子染成白色;
这里写图片描述
同理若白色比黑色多反过来;
这里写图片描述
如果黑色与白色一样多,那么要染的个数无论如何都是儿子结点数目/2,此时将父亲结点标记为待定,即染为黑色和染为白色都是一样的,交给上面结点决策。
这里写图片描述
举个例子
这里写图片描述
到了根节点待定还未确定,随便染颜色均可

动规的思路其他博客上写的很详细了,就简要提一提:
f[i,0]表示以i为根的子树中,根染黑色的最小染色数目
f[i,1]表示以i为根的子树中,根染白色的最小染色数目
f[i,0]表示以i为根的子树中,根不染色的最小染色数目
如果i不染色,在子结点中取一个最小的;i染黑色,子结点就不需要染黑色了,但白色的还是要染;白色同理


源代码

贪心:

#include<algorithm>
#include<iostream>
#include<iomanip>
#include<cstring>
#include<cstdlib>
#include<vector>
#include<cstdio>
#include<cmath>
#include<queue>
using namespace std;
inline const int Get_Int() {
    int num=0,bj=1;
    char x=getchar();
    while(x<'0'||x>'9') {
        if(x=='-')bj=-1;
        x=getchar();
    }
    while(x>='0'&&x<='9') {
        num=num*10+x-'0';
        x=getchar();
    }
    return num*bj;
}
int cnt=0,Head[50005],sum=1,n,Leaf,f[50005],InDegree[50005],root;
struct Edge {
    int to,next;
} Edge[50005];
void AddEdge(int from,int to) {
    cnt++;
    Edge[cnt].to=to;
    Edge[cnt].next=Head[from];
    Head[from]=cnt;
}
void TreeDp(int Now,int father) {
    if(Now<=Leaf)return;
    int cnt[3]= {0},color;
    for(int i=Head[Now]; i; i=Edge[i].next) {
        int Next=Edge[i].to;
        if(Next==father)continue;
        TreeDp(Next,Now);
        cnt[f[Next]]++;
    }
    if(cnt[0]==cnt[1])color=2;
    else if(cnt[1]>cnt[0])color=1;
    else color=0;
    f[Now]=color;
    sum+=min(cnt[0],cnt[1]);
}
int main() {
    n=Get_Int();
    Leaf=Get_Int();
    for(int i=1; i<=Leaf; i++)f[i]=Get_Int();
    for(int i=1; i<n; i++) {
        int x=Get_Int(),y=Get_Int();
        AddEdge(x,y);
        AddEdge(y,x);
        InDegree[x]++;
        InDegree[y]++;
    }
    for(int i=Leaf+1; i<=n; i++)
        if(InDegree[i]!=1) {
            root=i;
            break;
        }
    TreeDp(root,-1);
    printf("%d\n",sum);
    return 0;
}

动规:

#include<algorithm>
#include<iostream>
#include<iomanip>
#include<cstring>
#include<cstdlib>
#include<vector>
#include<cstdio>
#include<cmath>
#include<queue>
using namespace std;
inline const int Get_Int() {
    int num=0,bj=1;
    char x=getchar();
    while(x<'0'||x>'9') {
        if(x=='-')bj=-1;
        x=getchar();
    }
    while(x>='0'&&x<='9') {
        num=num*10+x-'0';
        x=getchar();
    }
    return num*bj;
}
int cnt=0,Head[50005],n,Leaf,f[50005][3];
struct Edge {
    int to,next;
} Edge[50005];
void AddEdge(int from,int to) {
    cnt++;
    Edge[cnt].to=to;
    Edge[cnt].next=Head[from];
    Head[from]=cnt;
}
void TreeDp(int Now,int father) {
    if(Now<=Leaf)return;
    for(int i=Head[Now]; i; i=Edge[i].next) {
        int Next=Edge[i].to;
        if(Next==father)continue;
        TreeDp(Next,Now);
        f[Now][0]+=min(f[Next][0]-1,min(f[Next][1],f[Next][2]));
        f[Now][1]+=min(f[Next][0],min(f[Next][1]-1,f[Next][2]));
        f[Now][2]+=min(f[Next][0],min(f[Next][1],f[Next][2]));
    }
    f[Now][0]++;
    f[Now][1]++;
}
int main() {
    n=Get_Int();
    Leaf=Get_Int();
    for(int i=1; i<=Leaf; i++) {
        int color=Get_Int();
        f[i][color]=1;
        f[i][(color+1)%2]=f[i][2]=0x7fffffff/2;
    }
    for(int i=1; i<n; i++) {
        int x=Get_Int(),y=Get_Int();
        AddEdge(x,y);
        AddEdge(y,x);
    }
    TreeDp(Leaf+1,-1);
    printf("%d\n",min(f[Leaf+1][0],min(f[Leaf+1][1],f[Leaf+1][2])));
    return 0;
}

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值