AGC006F Blackout

观察到题目中 (x,y)(y,z)=(z,x)(x, y) \oplus (y, z) = (z, x) 的特殊二元组生成方式,我们很容易联想到三元环,于是思考到能不能用图论解决这个问题。

具体在这个题目上,也就是给定了一个有向图,无重边有自环,一旦有 xy,yzx \to y, y\to z,我们能迭代出一条 zxz \to x 的边,问最后总图有多少边。

根据题意,这个图不一定联通,但明显地,对于整个图而言,所有的弱联通块之间可以独立处理答案,最终的答案是这些弱联通块答案的总和。所以我们接下来处理的对象都是单个弱联通块。

(弱联通块的意思就是:该块中所有边不考虑方向,那么这个块是联通的)

我们考虑最简单的情况:一条链,并且一定以 11 开头。

最简单的情况

我们发现可以至少迭代出这样三条边:31,42,533 \to 1, 4 \to 2, 5 \to 3.

然后发现:好像类似于一个大小为 33 的循环。于是想到一个方案:对节点染色,色彩也按照一个大小为 33 的周期循环。具体用公式表示(为方便,我们将节点 ii 的颜色表示为 cic_i):

如果存在一条边 uvu \to v,我们的染色要使

cv={cu+1cu{1,2}1cu=3c_v=\begin{cases}c_u + 1& c_u \in \{1, 2\}\\1 & c_u = 3\end{cases}

聪明的小伙伴发现这种染色方案可能无法实现。具体来说,染色的结果有三种情况:

  • 染色一切顺利,三个颜色染全;
  • 染色一切顺利,三个颜色没有染全;
  • 染色时出现矛盾(染色失败)。

以此图为例,染色情况为,11 染成 1122 染成 2233 染成 3344 染成 1155 染成 22。属于第一种情况,染色成功并且三个色彩都有。

这样一来,313 \to 1 对应颜色 313 \to 1424 \to 2 对应颜色 121 \to 2535 \to 3 对应颜色 232 \to 3

于是嘴一个做法:如果存在两个点 u,vu, v,使得 cu=1,cv=2c_u = 1, c_v = 2 或者 cu=2,cv=3c_u = 2, c_v = 3 或者 cu=3,cv=1c_u = 3, c_v = 1,那么就连一条 uvu \to v 的边,接下来为了叙述方便,我们将这种边叫做正边

哦,这下我们还发现,原来刚刚我们落了一条边没连:151 \to 5(形成的三元环为 31533 \to 1 \to 5 \to 3)。总共边数为 88

但刚刚的情况太简单,我们能否换成一般的图呢?

更一般的图

如图中的黑边就是原边,我们按照黑边将节点染色(图中的红字),然后再连接出所有正边(图中的绿边),既可以形成上方这个图。手动按照题目模拟一下,发现满足题意。

严格证明这个命题:染色结果为第一种情况时,所有正边的集合就是答案。

此时一定存在 x,y,zx, y, z 使得边 xyx \to yyzy \to z 存在(原因:三种颜色染全),我们能迭代一条 zxz \to x 的边,x,y,zx, y, z 形成三元环。明显此时所有正边就是答案。也就是只有这三个点时,命题肯定成立。

然后考虑归纳构造:如果当前图 GG 是满足命题的,那么我们加入一条正边 xpx \to p,其中 xx 是图 GG 中颜色为 11 的一个点,而 pp 可以是原图中的一个点,也可以是一个新点。由于cx=1c_x = 1,明显有 cp=2c_p = 2

此处特殊记号,设 [a][a] 表示 {ucu=a}\{u|c_u = a\}

我们有 [3]x,xp[3] \to x, x \to p,因此可以迭代出:p[3]p \to [3]

又有了 p[3],[3][1]p \to [3], [3] \to [1],因此可以迭代出:[1]p[1] \to p

于是发现:所有新的正边都连接了,连接的也都是正边。因此此时有命题成立。

然后我们发现,如果设 xx 颜色为 2233,结论是同理的;

如果设是一条 pxp \to x 的正边,结论也是同理的。

命题完全得证了。

但是别忘了,这是一种情况。还有两种情况,分别是什么?

  • 染色一切顺利,三个颜色没有染全;
  • 染色时出现矛盾(染色失败)。

看起来上面那个情况(也就是第二个情况)好处理一点。其实真的很好处理,直接给出答案,你迭代不出任何边,最终的答案就是原来有多少边就是多少。

证明非常简单。采用反证法证明不存在 xy,yzx \to y, y\to z 这种形式:如果存在这种形式那么就会有三种颜色。与题设矛盾。原命题得证。

这个命题得证直接说明迭代条件成立不了,寄!

于是来到最后一种情况:染色时出现矛盾

可能会有小伙伴想象不出来这种情况,画个图举例:

矛盾

对的,就是个简单的四元环。但是你会发现你没办法把它染色成功。

比如,uu 染成 11xx 染成 22yy 染成 33zz 染成 11,然后……zuz \to u 就寄了。。

接下来我们手动模拟一下这个图,然后你会得到一个这样的东西。。

矛盾结果

是的,什么边都能迭代出来!!

接下来开始证明:

在染色情况三中,迭代结果为完全图(包括自环)的边集。

命题:如果图中存在自环,那么迭代结果为完全图(包括自环)的边集。

证明:假设这个自环节点为 tt,那么和 tt 相邻的点 uu 都会因为 uttuu \to t \to t \to utt 连成双向边,因为 utuuu \to t \to u \to u 从而使得 uu 形成自环。然后因为 uu 是自环了,和 uu 相邻的 vv 肯定也会和 uu 形成双向边并且 vv 自身形成自环,证明和 u,tu, t 之间相同。最后因为弱联通,相邻明显会遍布所有点,于是所有点之间都会存在双向边,所有点都有自环。

接下来考虑一般的矛盾图,我们来证明迭代后一定会产生自环,这样就能证明结论了。

首先我们可以想到,一般的矛盾图中一定存在一个弱环(不考虑边的方向就是环),这个弱环会产生矛盾。

这个很好证明,也是反证法,如果不存在弱环,那就是个树,每个节点根据父节点染色就好了,能构造出成功的染色方案。

考虑这样一个矛盾环:

矛盾环

(注意和我最开始举的那个矛盾的例子是不一样的,边的方向不同)

首先这个环里肯定有类似 xyzx \to y \to z 的状态。好的叛逆的小伙伴马上发言了,那我马上构造一个没有任何 xyzx \to y \to z 的弱环。

那么,首先我可以证明,这个弱环的节点必须是偶数(奇数无法构造),偶数只可以构造出一种顶针的图:

顶针

这种类似的图我们都可以构造这样一个染色方案:cx=cz=cv=1,cy=ct=cu=2c_x = c_z = c_v = 1, c_y = c_t=c_u = 2。然后应该走第二种情况。

接下来继续证明。既然这个弱环存在 xyzx \to y \to z,那么我们就迭代出一条 zxz \to x 的边(在这个例子里是 424 \to 2)。

矛盾环2

于是我们接下来关注 4,1,24, 1, 2 组成的这个小弱环——它是一定存在矛盾的。(因为 4,2,34, 2, 3 这个弱环没有问题)

同理这个弱环一定有 xyzx \to y \to z,这个图中是 1421 \to 4 \to 2,我们连接上 zxz \to x, 这个图中是 212 \to 1

弱环3

然后接着我们抛弃掉 44,找到 xyzx \to y \to z1211 \to 2 \to 1,然后就 111 \to 1,喜闻乐见地得到自环。

从这个特殊再次推广到一般,我们会发现,对于染色矛盾的图:

  • 图中一定有弱环;
  • 矛盾的弱环中一定有 xyzx \to y \to z
  • 可以迭代出 zxz \to x,那么 yy 这个点就是正常的,抛弃掉 yy,剩下的弱环一定矛盾,再次寻找 xyzx \to y \to z,删掉 yy
  • 经过无数次迭代,最后一定会删点删到只剩一个点,这个时候自环出现了。

证明结束。

于是到这里,我们完美证明了三种染色情况对应的结果。

第一种染色情况:所有颜色为 11 的点向所有颜色为 22 的点连边,所有颜色为 22 的点向所有颜色为 33 的点连边,所有颜色为 33 的点向所有颜色为 11 的点连边。

这里我们设颜色为 11 的点共有 pp 个,颜色为 22 的点共有 qq 个,颜色为 33 的点共有 rr 个,那么这种情况的对应答案为 pq+qr+prpq + qr + pr

第二种染色情况:也就是原来的边数,直接比如是 mm。(mm 和题目中含义不同)

第三种染色情况:染色矛盾,此时应该是所有点都向所有点连边(包括自己),也就是点数的平方,可以说 n2n ^ 2。(nn 和题目中含义不同)

最后考虑我们如何染色。回顾一下怎么染色的吧。

如果存在一条边 uvu \to v,我们的染色要使

cv={cu+1cu{1,2}1cu=3c_v=\begin{cases}c_u + 1& c_u \in \{1, 2\}\\1 & c_u = 3\end{cases}

那么我们直接从一个点开始遍历出边 uvu \to v,然后对出点进行对应规则的染色就可以了。但问题在于我们给出的是弱联通块,任意一点甚至不能互相可达。

其实这个问题很好解决,初始建图时,对于 uvu \to v,我们建一条反边 vuv \to u 就可以了,遍历的时候,如果是正边,那么按照正常染色,如果是反边,那么按照反着循环染色,也就是:对于一条反边 uvu \to v,有

cv={cu1cu{2,3}3cu=1c_v=\begin{cases}c_u - 1& c_u \in \{2, 3\}\\3 & c_u = 1\end{cases}

于是这样可以保证任意一点互相可达。你是否想问可以不可以挨个没有遍历到的点为起点开始 dfs / bfs,其实稍微想一下就知道为什么不可以了:弱联通快新的部分起点应该以什么颜色开头?能否和弱联通块之前染色的那部分完美吻合?怎么区分是否在同一个弱联通块?很难处理。

相反我们应该将这种方式运用在不同联通块的处理上:如果没染色说明是另一个联通块。当然前提是运用了建反边把弱联通块变成联通块。

代码:

/*
 * @Author: crab-in-the-northeast 
 * @Date: 2022-07-25 02:05:23 
 * @Last Modified by: crab-in-the-northeast
 * @Last Modified time: 2022-07-25 03:15:21
 */
#include <bits/stdc++.h>
inline int read() {
    int x = 0;
    bool flag = true;
    char ch = getchar();
    while (!isdigit(ch)) {
        if (ch == '-')
            flag = false;
        ch = getchar();
    }
    while (isdigit(ch)) {
        x = (x << 1) + (x << 3) + ch - '0';
        ch = getchar();
    }
    if(flag)
        return x;
    return ~(x - 1);
}

const int maxm = (int)2e5 + 5; // 注意双倍建图要双倍数组大小
const int maxn = (int)1e5 + 5;

struct edge {
    int to, nxt, w;
}e[maxm];

int head[maxn], ecnt = 0;
inline void add_edge(int u, int v, int w) {
    e[++ecnt].to = v;
    e[ecnt].w = w;
    e[ecnt].nxt = head[u];
    head[u] = ecnt;
}

int c[maxn], cnt[5];
// c 数组代表第 i 个节点的颜色,注意程序中为了方便,染色使用0, 1, 2
// cnt 数组代表第 i 个颜色的节点数量,统计用

inline int cal(int u, int w) {
    return (u + w) % 3; // 计算节点 u 的后 w 个颜色
}

std :: queue <int> q;

int main() {
    std :: memset(c, -1, sizeof(c)); // -1 才表示未染色,因为 0 是颜色之一
    int m = read(), n = 0; // m 是题目中的 n,n 是题目中的 m
    while (m--) {
        int x = read(), y = read();
        add_edge(x, y, 1);
        add_edge(y, x, 2); // 在 %3 意义下相当于 -1,但是不用判断负数
        n = std :: max(n, std :: max(x, y)); // n 取最大的 x, y
    }

    long long ans = m = 0; // m 已经没有用了,这里我们用它来维护每个弱联通块的边数
    bool flag = false; // 染色是否矛盾
    for (int i = 1; i <= n; ++i) {
        if (c[i] != -1) // 如果不是新弱联通块就 continue
            continue;
        // 接下来都是从新的弱联通块中一个点开始 bfs
        cnt[0] = cnt[1] = cnt[2] = c[i] = m = 0;
        flag = false;
        q.push(i);
        while (!q.empty()) {
            int u = q.front();
            q.pop();
            ++cnt[c[u]]; // 统计 cnt
            for (int j = head[u]; j; j = e[j].nxt) {
                ++m; // 统计边
                int v = e[j].to;
                if (c[v] != -1) { // 已经染色
                    if (c[v] != cal(c[u], e[j].w)) // 矛盾
                    // 当前需要给他染的色和之前给他染的色不匹配
                        flag = true;
                } else { // 未染色
                    c[v] = cal(c[u], e[j].w); // 染色
                    q.push(v);
                }
            }
        }
        if (flag) // 情况三
            ans += 1LL * (cnt[0] + cnt[1] + cnt[2]) * (cnt[0] + cnt[1] + cnt[2]);
            // 明显地 cnt[0] + cnt[1] + cnt[2] 代表总共点数
        else if ((cnt[0] != 0) && (cnt[1] != 0) && (cnt[2] != 0)) // 情况一
            ans += 1LL * cnt[0] * cnt[1] + 1LL * cnt[1] * cnt[2] + 1LL * cnt[0] * cnt[2];
        else // 情况二
            ans += m >> 1; // 因为双倍建图所以实际边数应该是双倍的图中的边数除以2
    }
    printf("%lld\n", ans);
    return 0;
}

最终的归宿。是个结论题。

posted @   dbxxx  阅读(59)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示