HDU 6370 Werewolf 【推导 + 反向建图】 有趣的题

传送门

题意: 背景狼人杀, 不过场上的人只有两种身份, 一种是狼人, 一种是村民, 每个人知道其他人的身份, 村民只说真话, 狼人可能说真话, 现在给定这些人说的话, 问一定能确定身份为狼人和村民的各有多少个人,

证明: 证明的话我就用bestcoder的吧, 写的很好, 我就不详细说了.
注意: 因为是多起点有向图, 所以这里的树根是正向建图的叶子节点.

首先,“所有人都是狼人”是合法的,所以第一个答案一定是0。
那么我们的任务就是确认有多少人满足存在一种方案使得这个人是村民,其他人就是铁狼了(即为第二个要求输出的答案)
我们把所有人抽象成点:

  • 对于每句话,建立一条说话的人指向被说的人的一条有向边。
  • 如果该句话为说某人是狼人,就称这条边为狼人边。

暂时不考虑狼人边,把图分成若干联通块,这样每个联通块:


  • 要么点数和边数一致(基环树)
  • 要么点数比边树多1(树)

现在对于树,考虑狼人边(有且仅有一条),狼人边是树根连出去的。
如果狼人边指向的是其他联通块:

  • 那么让树中的人都是村民,其他人都是狼人显然也合法
  • 这些人都不是铁狼

如果指向树中的某个节点:
- 如果根是狼人,则其儿子就是狼人,以至于整棵树都是狼人,没有意义。
- 如果根是村民,则其指向的节点 x 是铁狼,则以 x 为根的子树都是铁狼。
- 而让树中的其它节点为村民,此时合法。
- 所以树中非 x子树的节点都可以是村民对于基环树,由于没有狼人边,所以让他们都是村民是合法的,这些人都不是铁狼

所以我们要求的就是村民边形成的树, 该树的根指向的那个点的树大小, 所以我们直接对于指向为村民边的反向建, 然后记录一下入度, 并且还要记录树根指向的那个点, 因为这是一条狼人边, 我们要统计的就是它指向的那个点的siz大小, 所以我们对于每个村民边形成的树dfs一下, 并且记录下siz和每个点属于的那棵树的根即可, 然后最后统计一下答案即可, 细节请看代码实现..

AC Code

const int maxn = 1e5+5;
int in[maxn], a[maxn], fa[maxn];
int siz[maxn]; vector<int>g[maxn];
void dfs(int u, int root) {
    siz[u] = 1;
    for (int i = 0 ; i < sz(g[u]) ; i ++) {
        dfs(g[u][i], root); fa[g[u][i]] = root;
        siz[u] += siz[g[u][i]];
    }
}
void solve() {
    int n; scanf("%d", &n);
    char s[20];
    for (int i = 1 ; i <= n ; i ++) {
        g[i].clear(); in[i] = 0; fa[i] = -1;
    }
    for (int i = 1 ; i <= n ; i ++) {
        int x; scanf("%d%s", &x, s);
        if (s[0] == 'w') a[i] = x;
        else {
            g[x].pb(i);
            ++in[i];
        }
    }
    for (int i = 1 ; i <= n ; i ++) {
        if (!in[i]) dfs(i, i);
    }
    int ans = 0;
    for (int i = 1 ; i <= n ; i ++) {
        if (in[i] || fa[a[i]] != i) continue;
        ans += siz[a[i]];
    }
    printf("0 %d\n", ans);
}

你可能感兴趣的:(树/图有关的各种经典问题,想法思维题)