2015-2016 ACM-ICPC Southwestern Europe Regional Contest (SWERC 15)

题目链接:https://codeforces.com/gym/101128

D - Dice Cup

题意:给两个骰子,分别是n面和m面,n面骰子的每个面依次是[1,n],且全部等概率。m面骰子同理。求那些数字出现的概率最高。

题解:签到题,随便做一下,但是uva要求的输出格式好恶心啊,对着cf的题目做直接出xiang。分两步步,枚举第一个骰子的结果和第二个骰子的结果,每次枚举到的情况,概率都增加 \(\frac{1}{nm}\) ,所以直接约掉这个分数。

int cnt[50];

void test_case() {
    int n, m;
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; ++i) {
        for(int j = 1; j <= m; ++j)
            cnt[i + j]++;
    }
    int mx = 0;
    for(int i = 1; i <= n + m; ++i)
        mx = max(mx, cnt[i]);
    for(int i = 1; i <= n + m; ++i) {
        if(cnt[i] == mx)
            printf("%d\n", i);
    }
}

H - Sheldon Numbers

题意:定义一种数,叫Sheldon Numbers,它的二进制表示为以下两种形式之一: \(ABABA...BA\) 或者 \(ABABA...AB\) 且至少有一个 \(A\) 串。 \(A\) 串为非空的 "1" 串, \(B\) 串为非空的 "0" 串。求 \([X,Y]\) 之间有多少个Sheldon Numbers。

题解:直接四重循环生成所有的Sheldon Numbers就可以了。

ull bits[70];

ull gen(int n, int m, int tn, int tm) {
    ull res = bits[n];
    --tn;
    while(1) {
        if(tm == 0 && tn == 0)
            return res;
        res <<= m;
        --tm;
        if(tm == 0 && tn == 0)
            return res;
        res <<= n;
        res |= bits[n];
        --tn;
    }
}

void test_case() {
    ull x, y;
    for(int i = 1; i <= 63; ++i)
        bits[i] = (1llu << i) - 1llu;
    while(cin >> x >> y) {
        int cnt = 0;
        for(int n = 1; n <= 63; ++n) {
            ull res = gen(n, 0, 1, 0);
            if(res >= x && res <= y)
                ++cnt;
            for(int i = 1; i <= 63; ++i) {
                if(i * n > 64)
                    break;
                for(int m = 1; m <= 63; ++m) {
                    for(int j = max(1, i - 1); j <= i; ++j) {
                        if(n * i + m * j > 64)
                            break;
                        ull res = gen(n, m, i, j);
                        if(res >= x && res <= y)
                            ++cnt;
                    }
                }
            }
        }
        cout << cnt << endl;
    }
}

A - Promotions

题意:有一个公司,有 \(n\) 个点 \(m\) 条有向边,有向边表示起点比终点强。三次独立询问,第一次有 \(A\) 个提升机会,问这次必定获得提升的有几个人?第二次有 \(B\) 个提升机会,问这次必定获得提升的有几个人?第三次问,即使在 \(B\) 个提升机会的前提下,还是没有任何可能提升的有几个人?题目保证是个DAG。

题解:什么情况下某个人是必定获得提升的?其实是在用完提升机会之前他成为了唯一的起点。因为 \(n\) 很小,那么可以对于每个人暴力一次。枚举到x,把除x之外的起点一个一个依次删除,直到x成为唯一的起点,求删除了多少次,假如需要提升到x的次数<=A次,则A次中一定可提升x,第二问B也是同理。第三问则要尽可能把比x的优秀的人提升(可以从反图的x开始dfs知道原图中能到x的点分别是哪些,假如这些点(包括x)个数<=B,则可以贪心提升到x。

vector<int> G[5005];
vector<int> H[5005];

int indeg[5005];

int cnt1[5005];
int cnt2[5005];

queue<int> Q;

bool vis[5005];

void dfs(int u, int x) {
    if(vis[u])
        return;
    vis[u] = 1;
    ++cnt2[x];
    for(auto &v : H[u])
        dfs(v, x);
}

void test_case() {
    int A, B, n, m;
    scanf("%d%d%d%d", &A, &B, &n, &m);
    for(int i = 1; i <= m; ++i) {
        int u, v;
        scanf("%d%d", &u, &v);
        ++u, ++v;
        G[u].push_back(v);
        H[v].push_back(u);
    }
    for(int i = 1; i <= n; ++i) {
        while(!Q.empty())
            Q.pop();
        for(int j = 1; j <= n; ++j) {
            indeg[j] = H[j].size();
            if(j != i && indeg[j] == 0)
                Q.push(j);
        }
        cnt1[i] = 1;
        while(!Q.empty()) {
            int u = Q.front();
            Q.pop();
            for(auto &v : G[u]) {
                --indeg[v];
                if(v != i && indeg[v] == 0)
                    Q.push(v);
            }
            ++cnt1[i];
        }
    }
    for(int i = 1; i <= n; ++i) {
        for(int j = 1; j <= n; ++j)
            vis[j] = 0;
        cnt2[i] = 0;
        dfs(i, i);
    }
    int ansA = 0, ansB = 0, ansC = 0;
    for(int i = 1; i <= n; ++i) {
        if(cnt1[i] <= A)
            ++ansA;
        if(cnt1[i] <= B)
            ++ansB;
        if(cnt2[i] <= B)
            ++ansC;
    }
    ansC = n - ansC;
    printf("%d\n%d\n%d\n", ansA, ansB, ansC);
}

这个常数好大哦,为什么。看了一下别人根本就不需要拓扑序,想一想也是,其实cnt1[x]是“不得不删除x时,已经删除的点的数量+1”,假如从x出发dfs其可达点,然后用n减去这些可达点,再+1,也会得到cnt1[x]。

vector<int> G[5005];
vector<int> H[5005];

int cnt1[5005];
int cnt2[5005];

bool vis[5005];

void dfs1(int u, int x) {
    if(vis[u])
        return;
    vis[u] = 1;
    ++cnt1[x];
    for(auto &v : G[u])
        dfs1(v, x);
}

void dfs2(int u, int x) {
    if(vis[u])
        return;
    vis[u] = 1;
    ++cnt2[x];
    for(auto &v : H[u])
        dfs2(v, x);
}

void test_case() {
    int A, B, n, m;
    scanf("%d%d%d%d", &A, &B, &n, &m);
    for(int i = 1; i <= m; ++i) {
        int u, v;
        scanf("%d%d", &u, &v);
        ++u, ++v;
        G[u].push_back(v);
        H[v].push_back(u);
    }
    for(int i = 1; i <= n; ++i) {
        for(int j = 1; j <= n; ++j)
            vis[j] = 0;
        cnt1[i] = 0;
        dfs1(i, i);
        cnt1[i] = n - cnt1[i] + 1;
    }
    for(int i = 1; i <= n; ++i) {
        for(int j = 1; j <= n; ++j)
            vis[j] = 0;
        cnt2[i] = 0;
        dfs2(i, i);
    }
    int ansA = 0, ansB = 0, ansC = 0;
    for(int i = 1; i <= n; ++i) {
        if(cnt1[i] <= A)
            ++ansA;
        if(cnt1[i] <= B)
            ++ansB;
        if(cnt2[i] <= B)
            ++ansC;
    }
    ansC = n - ansC;
    printf("%d\n%d\n%d\n", ansA, ansB, ansC);
}

收获:无论用不用容器,需要记录度数,计算度数,判断度数,然后还要下标偏移来偏移去的拓扑排序是不可能比得上dfs的速度的。

C - Canvas Painting

题意:给一堆数,可以在染色前调换成任意的顺序,然后根据某个规则开始染色。这个规则是:每次选择同样颜色的一段数字,然后把左边染成与之前的颜色都不同的新颜色X,右边染成与之前的颜色(包括左边的新颜色X)都不同的新颜色Y。花费的颜料为这段数字的和。求最少的颜料把所有数字染成不同的颜色。

题解:一开始不知道可以调换成任意的顺序,以为只有一种 \(O(n^3)\) 的区间dp。假如可以随意调换顺序的话,那么有一种思路:原本是把一个区间分裂成两个区间,花费他们和的代价,事实上和区间dp一样反过来变成把两个区间合并成一个区间,也是花费他们和的代价。要求和最小,假如每次可以随意合并两个数的话,结果就应该是哈夫曼树。这里的数字的顺序可以在一开始调换好,且两个数合并之后还是停留在原本的位置,所以总是存在一种顺序使得构造哈夫曼树的合并可行。具体来说,就是强制要求当前合并的两个区间,一个在左边,另一个在右边,注意到他们合并之后变成的新区间也会有新的左边和右边,所以总是可以这样安排的。

priority_queue<ll, vector<ll>, greater<ll> >PQ;
 
void test_case() {
    int n;
    scanf("%d", &n);
    while(n--) {
        int x;
        scanf("%d", &x);
        PQ.push(x);
    }
    ll sum = 0;
    while(PQ.size() > 1) {
        ll u = PQ.top();
        PQ.pop();
        ll v = PQ.top();
        PQ.pop();
        u += v;
        sum += u;
        PQ.push(u);
    }
    printf("%lld\n", sum);
    PQ.pop();
}

E - Wooden Signs

题意:有n块有左右方向区别的木牌,给一个n+1个数的排列(也就是说他们的顶端总是在不同的位置),第一个数表示第一块木牌的尾端,第二个数表示第一块木牌的顶端,保证第二个数比第一个数大,也就是第一块木牌总是向右的。从第二块木牌开始,假设现在是第i块木牌,第i块木牌的底端必须与第i-1块木牌的底端或者顶端对齐,由于题目的限制要打一个螺丝钉连接相邻的木牌,所以在某些情况下假如选择与第i-1块木牌的底端对齐,则一定会同向,假如选择与底端对齐则一定会反向。问这个序列对应多少种外形的木牌。

题解:看起来就很dp,因为每次只和最顶层的木牌有关。设 \(dp[i][0]\) 表示第i块木牌,方向向右的方法数, \(dp[i][1]\) 表示第i块木牌,方向向左的方法数。很明显看出只要方向确定了,顶层木牌的形状就是唯一的。(假)

写到一半发现假了,果然还是要 \(O(n^2)\) 的。设 \(dp[i][j]\) 表示第i块木牌,起始位置为j的方法数。

const int MOD = 2147483647;

int a[2005];
ll dp[2][2005];

void test_case() {
    int n;
    scanf("%d%d", &n, &a[0]);
    for(int i = 1; i <= n; ++i)
        scanf("%d", &a[i]);
    memset(dp, 0ll, sizeof(dp));
    dp[1][a[0]] = 1ll;
    for(int i = 2; i <= n; ++i) {
        int it = i & 1;
        memset(dp[it], 0ll, sizeof(dp[it]));
        int m = a[i];
        for(int b = 1; b <= n + 1; ++b) {
            if(b == a[i - 1])
                continue;
            int l = b, r = a[i - 1];
            if(l > r)
                swap(l, r);
            if(l < m && m < r) {
                dp[it][l] += dp[1 - it][b];
                dp[it][r] += dp[1 - it][b];
            } else if(m > r)
                dp[it][l] += dp[1 - it][b];
            else
                dp[it][r] += dp[1 - it][b];
        }
        for(int j = 1; j <= n + 1; ++j) {
            if(dp[it][j] >= MOD)
                dp[it][j] %= MOD;
        }
    }
    ll sum = 0;
    for(int j = 1; j <= n + 1; ++j)
        sum += dp[n & 1][j];
    printf("%lld\n", sum % MOD);
}

注意memset的时候,不是*(n+1)而是*(n+2),或者直接全部memset。

G - Game of Cards

仔细读题,别又读错了。

题意:有n堆牌堆,每堆牌都至少有一张牌,每张牌上面有一个数字。现在A和B轮流按下面的操作取牌,无法进行一次合法操作的玩家失败。

一次合法操作分为三个步骤:
1、选取一堆非空的牌堆x
2、从牌堆x上去除“牌堆x顶部的[0,min(k,n-1)]”张牌
3、从牌堆x上去除“牌堆x顶部的牌上写的数字的数量”张牌
4、若在3完成之前把该牌堆x取空,则是非法操作

题解:无法操作是输,看起来很像公平组合游戏。由SG定理,好像是把每堆牌的SG函数求出来然后异或出结果。那么按照求SG函数的定义,把每个状态的后继状态计算出来即可。注意排序去重或者改为set只会提升复杂度。看清楚题意,是每一步都可以选一个牌堆,再把上面的至多k张牌扔掉(不能扔完)。下面是fzr给的祖传SG模板,f[i]表示i状态的后继状态,注意这里的求法都是默认状态只能向前面的状态转移的(也就是取石子)。

int n, k;
int a[1005];
vector<int> f[1005];

void setState() {
    for(int i = 0; i <= n; ++i)
        f[i].clear();
    for(int i = 1; i <= n; ++i) {
        if(a[i] <= i)
            f[i].push_back(i - a[i]);
    }
    for(int ni = n; ni >= 1; --ni) {
        for(int ki = 1; ki <= min(k, ni - 1); ++ki) {
            int j = ni - ki;
            if(a[j] <= j)
                f[ni].push_back(j - a[j]);
        }
    }
}

int SG[1005], S[1005];
int getSG() {
    for(int i = 0; i <= n; ++i)
        SG[i] = 0;
    for(int i = 1; i <= n; i++) {
        int l = f[i].size();
        for(int j = 0; j <= l; j++)
            S[j] = 0;
        for(auto &j : f[i])
            S[SG[j]] = 1;
        for(int j = 0; j <= l; j++) {
            if(!S[j]) {
                SG[i] = j;
                break;
            }
        }
    }
    return SG[n];
}


void test_case() {
    int p;
    while(~scanf("%d%d", &p, &k)) {
        int SG = 0;
        for(int i = 1; i <= p; ++i) {
            scanf("%d", &n);
            for(int i = 1; i <= n; ++i)
                scanf("%d", &a[i]);
            setState();
            SG ^= getSG();
        }
        if(SG)
            puts("Alice can win.");
        else
            puts("Bob will win.");
    }
}

J - Saint John Festival

题意:给10000个红点,50000个黑点。求有多少个黑点在任意一个红点围成的三角形内(包括边界)。坐标范围是[0,2^30]。

题解:(看了题解才发现)显然只需要求出红点的凸包,问题就变成了有多少个黑点在凸包内,这个可以二分求解。

又扩展了几何板子。

B - Black Vienna

题意:有26个字母,抽出其中3个作为“三人帮”,剩下的23个字母任意分配给两个玩家。然后有n次询问,每次对一个玩家询问两个不同的字母,他会回答这两个字母有多少个在它手上。根据这些信息求出满足这些信息的“三人帮”有多少种组合方式。

错误解法:设想每个玩家有个26个点的无向图,初始都为灰色,表示“可以存在也可以不存在”。假如某两个字母在他手上的和恰好为1,那么在两者之间连一条边;假如某两个字母在他手上的和为0,那么两个点都标为白色,表示“不存在”;假如某两个字母在他手上的和为2,那么两个点都标为黑色,表示“存在”。在标之前先验证是否矛盾,矛盾直接退出。否则得到一张图,然后对某个灰色点定颜色,那么与它有相连的边的点必和它反色,若矛盾则这种染色方案不可行。经过至多2^26次枚举得到所有合法的图。假如某个图和另一个玩家的某些图不矛盾,那么就会贡献答案。再仔细想,这个图应该是一个由若干二分图组成的,每个连通分量就是一个二分图,其中可以把这个连通分量的颜色规定为最小标号的点的颜色,这样就变得可以给连通分量染色。

由此可以设计两个长度为26的布尔向量,分别表示两个玩家手上是否有某个字母,一种合法的方案要求两个玩家的布尔向量中总共有23个1且它们的“与”为0且它们与上面构造的这个图不矛盾。

但是这个算法貌似把问题复杂化了,本身我只需要验证某种情况的“三人帮”是否有解,这种算法直接从解来构造“三人帮”。

考虑反过来,三重循环枚举“三人帮”是哪三个,那么剩下23个字母,枚举一个玩家手中的,复杂度2^23,取反得到另一个玩家的。然后分别与对应的限制过一遍。(好假啊,复杂度明显就不对)

想了很多种,复杂度都是不对的。唯一一种复杂度对的,计算的却不是“三人帮”的组合数,而是合法的图数。

char s[15];
int top1, top2;
int lim1[55], lim2[55];
int cnt1[55], cnt2[55];

int BIT = (1 << 26) - 1;

bool check1(int x) {
    for(int i = 1; i <= top1; ++i) {
        if(cnt1[i] == 0) {
            if((x & lim1[i]) != 0)
                return 0;
        } else if(cnt1[i] == 1) {
            if((x & lim1[i]) == 0 || (x & lim1[i]) == lim1[i])
                return 0;
        } else {
            if((x & lim1[i]) != lim1[i])
                return 0;
        }
    }
    return 1;
}

bool check2(int x) {
    for(int i = 1; i <= top2; ++i) {
        if(cnt2[i] == 0) {
            if((x & lim2[i]) != 0)
                return 0;
        } else if(cnt2[i] == 1) {
            if((x & lim2[i]) == 0 || (x & lim2[i]) == lim2[i])
                return 0;
        } else {
            if((x & lim2[i]) != lim2[i])
                return 0;
        }
    }
    return 1;
}

bool valid[(1 << 26) + 5][4];

void test_case() {
    int n;
    scanf("%d", &n);
    top1 = 0, top2 = 0;
    for(int i = 1, p, c; i <= n; ++i) {
        scanf("%s%d%d", s, &p, &c);
        int bitmask = (1 << (s[0] - 'A')) | (1 << (s[1] - 'A'));
        if(p == 1) {
            ++top1;
            lim1[top1] = bitmask;
            cnt1[top1] = c;
        } else {
            ++top2;
            lim2[top2] = bitmask;
            cnt2[top2] = c;
        }
    }
    for(int x = 0; x < (1 << 26); ++x)
        valid[x][0] = check1(x);
    for(int y = 1; y <= 3; ++y) {
        for(int x = 0; x < (1 << 26); ++x) {
            valid[x][y] = 0;
            for(int i = 0; i < 26; ++i) {
                if(x & (1 << i)) {
                    if(valid[x ^ (1 << i)][y - 1]) {
                        valid[x][y] = 1;
                        break;
                    }
                }
            }
        }
    }

    int cnt = 0;
    for(int x = 0; x < (1 << 26); ++x) {
        if(check2(x))
            cnt += valid[BIT ^ x][3];
    }

    printf("%d\n", cnt);
}

题解:原来是要用并查集。一开始有考虑过并查集,但是不知道为什么后面就放弃了。三重循环枚举“三人帮”是哪三个,那么剩下的就一定要在这两个玩家手中。看起来很像“扩展域”并查集。把每个字母拆成两个点,例如字母A拆成 \(A_1\)\(A_2\) ,其中 \(A_i\) 表示A点属于玩家i,那么这两个信息本身就是矛盾的,不能合并到同一个等价类中,假如他们两个被合并了就是矛盾,那么信息 "AB 1 1" 意思就是A点属于玩家1和B点属于玩家2是等价信息,且A点属于玩家2和B点属于玩家1是等价信息.经过若干次操作把等价信息合并,然后看看是否矛盾。但是怎么处理“三人帮”的点呢,突然明白“扩展域”并查集的本质,原来就是“等价信息”,那么“三人帮”的点属于某个玩家这个必为假,必定为假的信息肯定都是等价信息,而正确的答案要求除了“三人帮”的点都是恰好一个为真。只需要统统连起来然后判断是否有一个除了“三人帮”的点两端接在了一起。

char s[15];
int top1, top2;
int u1[55], u2[55];
int v1[55], v2[55];
int w1[55], w2[55];

struct DisjointSetUnion {
    static const int MAXN = 105;
    int n, fa[MAXN + 5], rnk[MAXN + 5];

    void Init(int _n) {
        n = _n;
        for(int i = 1; i <= n; i++) {
            fa[i] = i;
            rnk[i] = 1;
        }
    }

    int Find(int u) {
        int r = fa[u];
        while(fa[r] != r)
            r = fa[r];
        int t;
        while(fa[u] != r) {
            t = fa[u];
            fa[u] = r;
            u = t;
        }
        return r;
    }

    bool Merge(int u, int v) {
        u = Find(u), v = Find(v);
        if(u == v)
            return false;
        else {
            if(rnk[u] < rnk[v])
                swap(u, v);
            fa[v] = u;
            rnk[u] += rnk[v];
            return true;
        }
    }
} dsu;

void test_case() {
    int n;
    scanf("%d", &n);
    top1 = 0, top2 = 0;
    for(int i = 1, p, c; i <= n; ++i) {
        scanf("%s%d%d", s, &p, &c);
        if(p == 1) {
            ++top1;
            u1[top1] = (s[0] - 'A') + 1;
            v1[top1] = (s[1] - 'A') + 1;
            w1[top1] = c;
        } else {
            ++top2;
            u2[top2] = (s[0] - 'A') + 1;
            v2[top2] = (s[1] - 'A') + 1;
            w2[top2] = c;
        }
    }
    int cnt = 0;
    int TRUE = 53, FALSE = 54;
    for(int X = 1; X <= 26; ++X) {
        for(int Y = X + 1; Y <= 26; ++Y) {
            for(int Z = Y + 1; Z <= 26; ++Z) {
                dsu.Init(54);
                dsu.Merge(X, X + 26);
                dsu.Merge(X, FALSE);
                dsu.Merge(Y, Y + 26);
                dsu.Merge(Y, FALSE);
                dsu.Merge(Z, Z + 26);
                dsu.Merge(Z, FALSE);
                int suc = 1;
                for(int i = 1; i <= top1; ++i) {
                    if(w1[i] == 0) {
                        dsu.Merge(u1[i], FALSE);
                        dsu.Merge(v1[i], FALSE);
                        if(u1[i] != X && u1[i] != Y && u1[i] != Z)
                            dsu.Merge(u1[i] + 26, TRUE);
                        if(v1[i] != X && v1[i] != Y && v1[i] != Z)
                            dsu.Merge(v1[i] + 26, TRUE);
                    } else if(w1[i] == 2) {
                        if(u1[i] == X || u1[i] == Y || u1[i] == Z) {
                            suc = 0;
                            break;
                        }
                        if(v1[i] == X || v1[i] == Y || v1[i] == Z) {
                            suc = 0;
                            break;
                        }
                        dsu.Merge(u1[i], TRUE);
                        dsu.Merge(v1[i], TRUE);
                        dsu.Merge(u1[i] + 26, FALSE);
                        dsu.Merge(v1[i] + 26, FALSE);
                    } else {
                        if((u1[i] == X || u1[i] == Y || u1[i] == Z) && (v1[i] == X || v1[i] == Y || v1[i] == Z)) {
                            suc = 0;
                            break;
                        }
                        if(u1[i] == X || u1[i] == Y || u1[i] == Z) {
                            dsu.Merge(v1[i], TRUE);
                            continue;
                        }
                        if(v1[i] == X || v1[i] == Y || v1[i] == Z) {
                            dsu.Merge(u1[i], TRUE);
                            continue;
                        }
                        dsu.Merge(u1[i], v1[i] + 26);
                        dsu.Merge(v1[i], u1[i] + 26);
                    }
                }
                if(!suc)
                    continue;
                for(int i = 1; i <= top2; ++i) {
                    if(w2[i] == 0) {
                        dsu.Merge(u2[i] + 26, FALSE);
                        dsu.Merge(v2[i] + 26, FALSE);
                        if(u2[i] != X && u2[i] != Y && u2[i] != Z)
                            dsu.Merge(u2[i], TRUE);
                        if(v2[i] != X && v2[i] != Y && v2[i] != Z)
                            dsu.Merge(v2[i], TRUE);
                    } else if(w2[i] == 2) {
                        if(u2[i] == X || u2[i] == Y || u2[i] == Z) {
                            suc = 0;
                            break;
                        }
                        if(v2[i] == X || v2[i] == Y || v2[i] == Z) {
                            suc = 0;
                            break;
                        }
                        dsu.Merge(u2[i] + 26, TRUE);
                        dsu.Merge(v2[i] + 26, TRUE);
                        dsu.Merge(u2[i], FALSE);
                        dsu.Merge(v2[i], FALSE);
                    } else {
                        if((u2[i] == X || u2[i] == Y || u2[i] == Z) && (v2[i] == X || v2[i] == Y || v2[i] == Z)) {
                            suc = 0;
                            break;
                        }
                        if(u2[i] == X || u2[i] == Y || u2[i] == Z) {
                            dsu.Merge(v2[i] + 26, TRUE);
                            continue;
                        }
                        if(v2[i] == X || v2[i] == Y || v2[i] == Z) {
                            dsu.Merge(u2[i] + 26, TRUE);
                            continue;
                        }
                        dsu.Merge(u2[i], v2[i] + 26);
                        dsu.Merge(v2[i], u2[i] + 26);
                    }
                }
                if(!suc)
                    continue;
                for(int i = 1; i <= 26; ++i) {
                    if(i == X || i == Y || i == Z)
                        continue;
                    if(dsu.Find(i) == dsu.Find(i + 26)) {
                        suc = 0;
                        break;
                    }
                }
                if(!suc)
                    continue;
                if(dsu.Find(TRUE) == dsu.Find(FALSE))
                    suc = 0;
                cnt += suc;
            }
        }
    }
    printf("%d\n", cnt);
}

收获:注意思考定义并查集时规定的每个节点的意义,当两个点至少其中之一是特殊点(“三人帮”)时不能够视为等价信息。

posted @ 2020-02-11 17:04  KisekiPurin2019  阅读(290)  评论(0编辑  收藏  举报