luogu P2341 [USACO03FALL][HAOI2006]受欢迎的牛 G(强连通分支模板 + 缩点)

题目背景

本题测试数据已修复。

题目描述

每头奶牛都梦想成为牛棚里的明星。被所有奶牛喜欢的奶牛就是一头明星奶牛。所有奶牛都是自恋狂,每头奶牛总是喜欢自己的。奶牛之间的“喜欢”是可以传递的——如果 AA 喜欢 BB,BB 喜欢 CC,那么 AA 也喜欢 CC。牛栏里共有 NN 头奶牛,给定一些奶牛之间的爱慕关系,请你算出有多少头奶牛可以当明星。

输入格式

第一行:两个用空格分开的整数:NN 和 MM。

接下来 MM 行:每行两个用空格分开的整数:AA 和 BB,表示 AA 喜欢 BB。

输出格式

一行单独一个整数,表示明星奶牛的数量。

输入输出样例

输入 #1复制

3 3
1 2
2 1
2 3

输出 #1复制

1

说明/提示

只有 3 号奶牛可以做明星。

【数据范围】

对于 10% 的数据,N≤20,M≤50。

对于 30% 的数据,N≤103,M≤2×104。

对于 70% 的数据,N≤5×103,M≤5×104。

对于100% 的数据,1≤N≤104,41≤M≤5×104。

强连通分量的板子题。

把同一强连通分量中的点缩为一个点,找到出度为0的强连通分量,该强连通分量中的所有点都是被所有牛喜欢的,都是最受欢迎的牛,如果有多个出度为0的强连通分量,说明原图不连通,没有任何一头牛被所有牛喜欢。

Kosaraju 算法,复杂度 O(N+M) ,视频讲解

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int N = 1e4 + 10;
const int M = 5e4 + 10;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;

struct Edge {
    int to, next;
}edge1[M], edge2[M];    ///原图和逆图
int head1[N], head2[N];
bool mark1[N], mark2[N];
int tot1, tot2;
int cnt1, cnt2;
int st[N];  ///时间戳
int belong[N];
int num;   ///中间变量,用来数某个连通分量中点的个数
int setnum[N];///强连通分量中点的个数,编号 0∼cnt2-1
int n, m;

void addedge(int u, int v) {
    edge1[tot1].to = v;
    edge1[tot1].next = head1[u];
    head1[u] = tot1++;

    edge2[tot2].to = u;
    edge2[tot2].next = head2[v];
    head2[v] = tot2++;
}

void dfs1(int u) {
    mark1[u] = 1;
    for(int i = head1[u]; ~i; i = edge1[i].next)
        if(!mark1[edge1[i].to])
            dfs1(edge1[i].to);
    st[cnt1++] = u;

}

void dfs2(int u) {
    mark2[u] = 1;
    num++;
    belong[u] = cnt2;
    for(int i = head2[u]; ~i; i = edge2[i].next)
        if(!mark2[edge2[i].to])
            dfs2(edge2[i].to);
}

void solve(int n) {
    memset(mark1, 0, sizeof(mark1));
    memset(mark2, 0, sizeof(mark2));
    cnt1 = cnt2 = 0;
    for(int i = 1; i <= n; ++i)         ///dfs原图求时间戳
        if(!mark1[i])
            dfs1(i);
    for(int i = cnt1 - 1; i >= 0; --i) { ///dfs逆图
        if(!mark2[st[i]]) {
            num = 0;
            dfs2(st[i]);
            setnum[cnt2++] = num;
        }
    }
}
int deg[N];

void init() {
    tot1 = tot2 = 0;
    memset(head1, -1, sizeof(head1));
    memset(head2, -1, sizeof(head2));
    memset(deg, 0, sizeof(deg));
}


int main() {
    init();
    int u, v;
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= m; ++i) {
        scanf("%d%d", &u, &v);
        addedge(u, v);
    }
    solve(n);
    ///缩点 同一强连通分量的点缩为1个点,下标就是强连通分量的标号
    for(int i = 1; i <= n; ++i) {
        for(int j = head2[i]; ~j; j = edge2[j].next) {
            if(belong[i] != belong[edge2[j].to])
                deg[belong[edge2[j].to]]++; ///edge2是逆图,入点的出度++
        }
    }
    int ans = 0;
    for(int i = 0; i < cnt2; ++i) { ///cnt2是强连通分量的个数
        if(deg[i] == 0) {           ///找到出度为0的点
            if(ans) {               ///如果缩点后图中有 > 1个出度为0的点,说明原图不连通,没有一头牛被所有牛喜欢
                printf("0\n");
                return 0;
            }
            ans = setnum[i];        ///该强连通分量中的牛都被所有牛喜欢
        }
    }
    printf("%d\n", ans);
    return 0;
}

Tarjan 算法,复杂度 O(N+M) ,视频讲解

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int N = 1e4 + 10;
const int M = 5e4 + 10;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;

struct Edge {
    int to, next;
}edge[M];

int head[N], tot, n, m;
int low[N], dfn[N], stk[N], belong[N];///dfn时间戳 stk栈 belong属于哪个强连通分量
int tim, top, scc;    ///tim时间戳 scc强连通分量个数
bool instack[N];      ///标记是否已在栈中
int num[N];           ///每个强连通分量大小

void addedge(int u, int v) {
    edge[tot].to = v;
    edge[tot].next = head[u];
    head[u] = tot++;
}

void Tarjin(int u) {
    int v;
    low[u] = dfn[u] = ++tim;
    stk[top++] = u;
    instack[u] = 1;
    for(int i = head[u]; ~i; i = edge[i].next) {
        v = edge[i].to;
        if(!dfn[v]) {
            Tarjin(v);
            if(low[u] > low[v]) low[u] = low[v];    ///发现回路 更新
        }
        else if(instack[v] && low[u] > dfn[v])
            low[u] = dfn[v];
    }
    if(low[u] == dfn[u]) {    ///发现一个强连通分量
        scc++;
        do {
            v = stk[--top];
            instack[v] = 0;
            belong[v] = scc;
            num[scc]++;
        }
        while(v != u);        ///回溯
    }
}

void solve(int n) {
    memset(dfn, 0, sizeof(dfn));
    memset(instack, 0, sizeof(instack));
    memset(num, 0, sizeof(num));
    tim = scc = top = 0;
    for(int i = 1; i <= n; ++i)
        if(!dfn[i])
            Tarjin(i);
}

void init() {
    tot = 0;
    memset(head, -1, sizeof(head));
}

int du[N];

int main() {
    init();
    int u, v;
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= m; ++i) {
        scanf("%d%d", &u, &v);
        addedge(u, v);
    }
    solve(n);
    for(int i = 1; i <= n; ++i) {
        for(int j = head[i]; ~j; j = edge[j].next) {
            if(belong[i] != belong[edge[j].to]) {
                du[belong[i]]++;
            }
        }
    }
    int ans = 0;
    for(int i = 1; i <= scc; ++i) {
        if(du[i] == 0) {
            if(ans) {
                printf("0\n");
                return 0;
            }
            ans = num[i];
        }
    }
    printf("%d\n", ans);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_43871207/article/details/109085193