集合划分 题解

一、题目:

二、思路:

这道题思路挺清奇的。属于那种既考算法模板又考思维的一类题。

看到一个点,可以放在\(A\)\(B\)两个集合,就要联想到2-SAT问题。

这道题如果只让判断是否存在合法方案,并输出任意一种方案,那这题就属于2-SAT问题的模板。

但是这题比较难的一点就是求方案数。

一般的2-SAT问题能不能求方案数呢?我在网上搜了一下,没有找到满意的答案。

那么这道题肯定就是要挖掘题目的性质了。

考虑现在已经用普通的Tarjan算法求出来一种合法方案了,用这一种合法方案去求出其他的合法方案。那么无非就是试图将A集合中的元素放到B集合中,或者将B集合中的元素放到A集合中,或者交换一下A集合、B集合中的元素呗。

一个很重要的性质:我们发现每次最多只能将A集合中的一个元素放到B集合中。形式化一点,假设我们已经求出来一种合法方案\(S\),那么一定不会存在这样的合法方案\(T\),使得存在两个元素\(x, y\), 在\(T\)这种方案下\(x, y\)\(B\)集合中,但在\(S\)这种方案下\(x,y\)却处于\(A\)集合中。

原因是因为如果\(x,y\)都在\(A\)集合中,那么说明\(x,y\)之间一定有边,那么\(x,y\)就不能放在\(B\)集合中了。

反之,也一定不会存在这样的合法方案\(T'\),使得存在两个元素\(x,y\),在\(T'\)这种方案下\(x,y\)\(A\)集合中,但在\(S\)这种方案下\(x,y\)却处于\(B\)集合中。原因是类似的。

所以,要想从已知的合法方案\(S\),构造出其他合法方案\(T\),只有三种情况。

  1. \(A\)中取出一个元素\(x\),丢到\(B\)中去。需满足的条件:所有与\(x\)相连的点\(y\),都必须在\(A\)中。
  2. \(B\)中取出一个元素\(y\),丢到\(A\)中去。需满足的条件:所有与\(x\)不相连的点\(y\),都必须在\(B\)中。
  3. \(A\)中取出一个元素\(x\),从\(B\)中取出一个元素\(y\),交换\(x,y\)。需满足的条件:\(\forall (x,u)\in E\),且\(u\neq y\),都有\(u\in A\)\(\forall (y, v)\notin E\),且\(v\neq x\),都有\(v\in B\)

时间复杂度:\(O(n^2)\)

然后呢?然后就做完了。

三、实现细节:

  • 注意枚举交换\(x,y\)的时候稍微维护一下必要的信息,尽量不要让复杂度上升到\(O(n^3)\)及以上。
  • 注意判断无解的情况。

四、代码:

#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
#include <queue>
#include <set>

using namespace std;
#define FILEIN(s) freopen(s".in", "r", stdin)
#define FILEOUT(s) freopen(s".out", "w", stdout)

inline int read(void) {
    int x = 0, f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
    while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
    return f * x;
}

const int maxn = 5005, maxm = maxn * maxn * 2;

int n, head[maxn << 1], tot;
bool G[maxn][maxn], OK = true;
bool ins[maxn << 1];
int dfn[maxn << 1], low[maxn << 1], idx, num, cnt, stk[maxn << 1], top;
int ans[maxn << 1];
int tmp[maxn];
int p[maxn];

struct Edge {
    int y, next;
    Edge() {}
    Edge(int _y, int _next) : y(_y), next(_next) {}
} e[maxm];

set<int>S;

inline void connect(int x, int y) {
    e[++ tot] = Edge(y, head[x]);
    head[x] = tot;
}

void tarjan(int x) {
    dfn[x] = low[x] = ++ num;
    stk[++ top] = x; ins[x] = true;
    for (int i = head[x]; i; i = e[i].next) {
        int y = e[i].y;
        if (!dfn[y]) {
            tarjan(y);
            low[x] = min(low[x], low[y]);
        } else if (ins[y]) low[x] = min(low[x], dfn[y]);
    }
    if (low[x] == dfn[x]) {
        int y; ++ cnt; S.clear();
        do {
            y = stk[top --]; ins[y] = false;
            if (S.find(y) != S.end()) OK = false;

            if (y > n && ans[y - n] == -1) ans[y - n] = 1;
            if (y <= n && ans[y] == -1) ans[y] = 0;
            if (y > n) S.insert(y - n);
            if (y <= n) S.insert(y);
        } while (y != x);
    }
}

inline void solve(void) {
    int res1 = 0, res2 = 0;
    int siz1 = 0, siz2 = 0;
    for (int i = 1; i <= n; ++ i) {
        if (ans[i] == 0) {
            ++ siz1;
            bool flag = true;
            for (int j = 1; j <= n; ++ j) {
                if (i == j) continue;
                if (G[i][j] && ans[j] == 1) flag = false, ++ tmp[i], p[i] = j;
            }
            if (flag) {
                ++ res1;
            }
        }
        else { // ans[i] == 1
            ++ siz2;
            bool flag = true;
            for (int j = 1; j <= n; ++ j) {
                if (i == j) continue;
                if (!G[i][j] && ans[j] == 0) flag = false, ++ tmp[i], p[i] = j;
            }
            if (flag) {
                ++ res2;
            }
        }
    }
    long long sum = 0;
    long long res3 = 1LL * res1 * res2;
    for (int i = 1; i <= n; ++ i) {
        if (tmp[i] != 1) continue;
        int cur = p[i];
        if (tmp[cur] == 1 && p[cur] == i && cur > i) ++ res3;
        if (tmp[cur] == 0) ++ res3;
    }
    if (siz1 != 1) sum += res1;
    if (siz2 != 1) sum += res2;
    if (siz1 != 0 && siz2 != 0) sum += res3 + 1;
    printf("%lld\n", sum);
}

int main() {
    FILEIN("conspiracy"); FILEOUT("conspiracy");
    n = read();
    memset(ans, -1, sizeof ans);
    for (int i = 1; i <= n; ++ i) {
        int cnt = read();
        while (cnt --) {
            int x = read(); G[i][x] = G[x][i] = true;
        }
    }
    for (int i = 1; i <= n; ++ i) {
        for (int j = i + 1; j <= n; ++ j) {
            if (G[i][j]) {
                connect(i + n, j);
                connect(j + n, i);
            } else {
                connect(i, j + n);
                connect(j, i + n);
            }
        }
    }
    for (int i = 1; i <= 2 * n; ++ i) if (!dfn[i]) tarjan(i);
    if (!OK) { puts("0"); return 0; }
    solve();
    return 0;
}

posted @ 2021-03-06 16:23  蓝田日暖玉生烟  阅读(105)  评论(0编辑  收藏  举报