【题解】UVa 1220 Party at Hali-Bula

4 篇文章 0 订阅
3 篇文章 0 订阅

UVa传送门
洛谷RemoteJudge传送门
题目大意:公司里面有n(n ≤ 200)个 人形成一个树结构,即除了老板之外的每个员工都有且仅有一个直属上司。要求选尽量多的人,但不能同时选择一个人和他的直属上司。
问:1.最多可以选多少人。
2.方案是否唯一。
这道题就是典型的树的最大独立集问题

树的最大独立集问题

先讲一种解决方法。我们用 d(i) d ( i ) 表示以 i i 为根节点的子树的最大独立集大小。需要注意的是,在解决问题前,需要将无根树转换成有根树,这样上述的状态才有意义。
结点i一共有两种决策,选和不选,对应地,就有两个方程
如果不选i d(i)=js(i)d(j) d ( i ) = ∑ j ∈ s ( i ) d ( j )
如果选 i i d(i)=1+jgs(i)d(j)
接下来对两个答案取个 max m a x 就可以了。
代码实现的时候要注意一点,就是不能从 i i 去枚举i的儿子结点和孙子结点,而要从 i i 的儿子节点和孙子结点去找i。换句话说,就是计算出一个d(i)之后,去更新 d(father(i)) d ( f a t h e r ( i ) ) d(father(father(i))) d ( f a t h e r ( f a t h e r ( i ) ) )

看懂了上面一种,我们再讲一种更加实用的办法
我们用 d(i,0) d ( i , 0 ) 表示以 i i 为根的子树中,不选i点所能取到的最多的顶点数目。相应地,我们用 d(i,1) d ( i , 1 ) 表示以 i i 为根的子树中,选i点能取到的最多节点数目。
由于有两种状态,所以有两个转移方程。
d(u,0)=max(d(v,1),d(v,0)) d ( u , 0 ) = ∑ m a x ( d ( v , 1 ) , d ( v , 0 ) )
d(u,1)=d(v,0)+1 d ( u , 1 ) = ∑ d ( v , 0 ) + 1
其中 v v u的子节点。

有了刚才的模型,我们再来看这道题。
可以发现这道题几乎就是树的最大独立集问题,但还添加了一个方案是否唯一的询问。
我们采取第二种解决的方案,并令 f(i,0) f ( i , 0 ) 表示取到 d(i,0) d ( i , 0 ) 方案的唯一性,相应地, f(i,1) f ( i , 1 ) 表示取到 d(i,1) d ( i , 1 ) 方案的唯一性。
我们用 u u 表示当前的结点,v u u 的子节点。
f(u,0) 当某个 d(v,0) d ( v , 0 ) d(v,1) d ( v , 1 ) 相等或 max m a x 操作取到的那个对应值的 f==0 f == 0 时取 false f a l s e ,否则为 true t r u e
f(u,1) f ( u , 1 ) 则是对所有 f(v,0) f ( v , 0 ) ,换言之,当且仅当所有的 f(v,0)=1 f ( v , 0 ) = 1 f(u,1) f ( u , 1 ) 才是 1 1 .
Ans就保存在 max(d(r,0),d(r,1)) m a x ( d ( r , 0 ) , d ( r , 1 ) ) max(f(r,0),f(r,1)) m a x ( f ( r , 0 ) , f ( r , 1 ) ) 中, r r 就是那个可以任意选定的根。

Code

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector> 
#include<map>
#define MAXN 500
struct EdgeType {
    int to, next;
};
std::map< std::string, int > M;
std::vector< EdgeType > edge;
int head[MAXN], d[MAXN][2], num;
bool f[MAXN][2];
inline int ID(const std::string& rhs) {
    if (!M.count(rhs)) M[rhs] = ++num;
    return M[rhs];
}
inline void AddEdge(int from, int to) {
    edge.push_back((EdgeType){to, head[from]});
    head[from] = edge.size() - 1;
}
void solve(int u) {
    for (int i = head[u]; i != -1; i = edge[i].next) {
        int v = edge[i].to;
        solve(v);
        int temp = d[v][0] > d[v][1] ? 0 : 1;
        d[u][0] += d[v][temp];
        f[u][0] = f[u][0] & f[v][temp] & (d[v][0] != d[v][1]);
        d[u][1] += d[v][0];
        f[u][1] &= f[v][0];
    }
}
int main() {
    int n;
    std::string employee, boss;
    while (std::cin >> n >> boss) {
        M.clear();
        num = 0;        
        memset(head, -1, sizeof head);
        memset(f, true, sizeof f);
        for (int i = 1; i <= n; i++) 
            d[i][1] = 1, d[i][0] = 0;
        edge.clear();
        ID(boss);
        for (int i = 1; i < n; i++) {
            std::cin >> employee >> boss;
            AddEdge(ID(boss), ID(employee)); 
        }
        solve(1);
        int temp = d[1][0] > d[1][1] ? 0 : 1;
        printf("%d ", d[1][temp]);
        if (f[1][temp] && d[1][0] != d[1][1]) printf("Yes\n");
        else printf("No\n");
    }
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值