[CQOI2013]图的逆变换

题意

给一个n结点m条边的有向图D,可以这样构造图E:给D的每条边u->v,在E中建立一个点uv,然后对于D中的两条边u->v和v->w,在E中从uv向vw连一条有向边。E中不含有其他点和边。
输入E,你的任务是判断是否存在相应的D。注意,D可以有重边和自环。

测试数据个数 T10
D 的边数(即 E 的点数) m300
Time Limits:2000ms
Memory Limits:512000KB

分析

Outu E u 点出边指向的点的集合。我们会有一个结论:若存在 D , u,v Outu=Outv or OutuOutv=ϕ
结论必要性显然,充分性引用题解里的证明:可以尝试构造出一个可能的图 G。对于每一个 u,找到的若干 x, y的导出子图是一个完全二分图,对左部在图 G 中对应的边到达的点和右部在图 G 中对应的边出发的点规定为 G 中一个全新的节点即可。
这样我们可以用并查集,对于点 u ,将 Outu 里的点并一起,并记录并查集的大小。最后扫一遍,看每个点 Out 集合大小是否跟并查集大小相等。

代码

#include <cstdio>
#include <cstring>
using namespace std;

const int N = 310,M = 1e6;
int g[N],next[M],to[M],d[N],f[N],size[N];
int tot,n,k;

void add(int x,int y) {
    to[++ tot] = y;
    next[tot] = g[x];
    g[x] = tot;
    d[x] ++;
}

int get(int x) {
    if (f[x] != x) f[x] = get(f[x]);
    return f[x];
}

int main() {
    int T;
    scanf("%d",&T);
    while (T --) {
        memset(g,0,sizeof(g));
        memset(d,0,sizeof(d));
        tot = 0;
        scanf("%d%d",&n,&k);
        for (int i = 0;i < n;i ++) f[i] = i,size[i] = 1;
        for (int i = 1;i <= k;i ++) {
            int x,y;
            scanf("%d%d",&x,&y);
            add(x,y);
        }
        for (int i = 0;i < n;i ++) if (g[i]) {
            int j,last = to[g[i]];
            int y = get(last);
            for (j = next[g[i]];j;j = next[j]) {
                int x = get(to[j]);
                if (x == y) continue;
                f[x] = y;
                size[y] += size[x];
            }
        }
        int flag = 1;
        for (int i = 0;i < n;i ++) if (g[i]) {
            int j = get(to[g[i]]);
            if (size[j] != d[i]) {
                flag = 0;
                break;
            }
        }
        if (flag) printf("Yes\n");
        else printf("No\n");
    }
}

你可能感兴趣的:([CQOI2013]图的逆变换)