其他分享
首页 > 其他分享> > 真正的骗子(种类并查集)

真正的骗子(种类并查集)

作者:互联网

题目链接

  思路:
    分成两类:1.村民说真话,2.村民说假话。当村民说是好人的时候,有两种情况,他们都是好人和都是坏人。所以将\(a\ + \ (x + y) ,\ b\ +\ (x + y)\)和\(a, b\)合并为一个集合。同理将\(a,b+(x + y)\)和\(a + (x + y), b\)合并为一个集合。
    这个合并的过程我们就可以采用种类并查集的方式来将他们合并起来.

        std::function<int(int)> find = [&] (int x) -> int {
            return x == f[x] ? f[x] : f[x] = find(f[x]);  
        };
            
        auto merge = [&] (int u, int v) -> void {
            u = find(u), v = find(v);
            if (u == v) return ;
            //按秩合并
            if (dep[u] > dep[v]) f[v] = u, s[u] += s[v];
            else if (dep[u] < dep[v]) f[u] = v, s[v] += s[u];
            else f[v] = u, s[u] += s[v], dep[u]++;
        };
        
        //种类并查集合并
        for (int i = 1; i <= n; i++) {
            int a, b;
            char op[5];
            std::cin >> a >> b >> op;
            if (op[0] == 'y') merge(a, b), merge(a + res, b + res);
            else merge(a, b + res), merge(a + res, b);
        }
        
        //统计有多少个集合
        int cnt = 0;
        for (int i = 1, fa; i <= res; i ++) {
            if ((fa = find(i)) != i) continue;
            vis[fa] = vis[fa + res] = ++cnt;
            p[cnt] = s[fa], q[cnt] = s[fa + res];
        }

    要判断我们能否知道每个人的身份的话,也就是我们要凑出来有\(x\)个人是好人,剩下的\(y\)个人是坏人,我们提前已经将所有的人都分成了两个集合,好人集合和坏人集合,那么考虑用\(dp\)来将所有的状态来进行转移,类比于背包求解方案数的问题.定义\(dp[i][j]\)表示的是前\(i\)个连通块中有\(j\)个好人的方案是否存在,如果\(dp[i][j] == 1\)那就是这个方案可行,否则不然.

        dp[0][0] = 1;
        for (int i = 1; i <= cnt; i++) {
            for (int j = std::min(p[i], q[i]); j <= x; j++) {
                if (j >= p[i]) dp[i][j] += dp[i - 1][j - p[i]];
                if (j >= q[i]) dp[i][j] += dp[i - 1][j - q[i]];
            }
        }
        
        if (dp[cnt][x] != 1) {
            std::cout << "no\n";
            continue;
        }
        
        for (int i = cnt; i; i--) {
            if (dp[i - 1][x - p[i]]) x -= p[i], p[i] = -1;
            else if (dp[i - 1][x - q[i]]) x -= q[i], p[i] = -2;
        }

    最后输出方案中的所有的好人

        dp[0][0] = 1;
        for (int i = 1; i <= cnt; i++) {
            for (int j = std::min(p[i], q[i]); j <= x; j++) {
                if (j >= p[i]) dp[i][j] += dp[i - 1][j - p[i]];
                if (j >= q[i]) dp[i][j] += dp[i - 1][j - q[i]];
            }
        }
        
        if (dp[cnt][x] != 1) {
            std::cout << "no\n";
            continue;
        }
        
        for (int i = cnt; i; i--) {
            if (dp[i - 1][x - p[i]]) x -= p[i], p[i] = -1;
            else if (dp[i - 1][x - q[i]]) x -= q[i], p[i] = -2;
        }

    多组测试注意初始化

        int res = x + y;
        memset(dp, 0, sizeof dp);
        for (int i = 1; i <= res; i++) {
            f[i] = i, f[i + res] = i + res;
            dep[i] = dep[i + res] = s[i] = 1;
            s[i + res] = vis[i] = vis[i + res] = 0;
        }
        for (int i = 1; i <= res; i++) s[i] = 1;

标签:dep,int,res,查集,merge,种类,骗子,集合,dp
来源: https://www.cnblogs.com/Haven-/p/16633624.html