LuoguP4306连通数 Tarjan

只谈情不闲聊 提交于 2019-12-04 04:11:00

https://www.luogu.org/problem/P4306       https://www.lydsy.com/JudgeOnline/problem.php?id=2208

这道题里面还有缩点+拓扑排序

话不多说直接上代码,代码里有详细解释,看不懂的先去看看搜索树方面的知识,lyd书上有

#include <bits/stdc++.h>
using namespace std;
int n, ans, cnt, sum, tot;//tot记录图中有几个强连通分量
bool v[2021];//判断节点是否被访问过
int dfn[2021];//节点i搜索的次序编号(时间戳)每个点第一次被访问的时间顺序
int low[2021];//表示u或u的子树能够追溯到的最早的栈中节点的次序号,时间戳的最小值
int scc[2021];//表示 x 所在的强连通分量的编号
int deg[2021];//储存强连通分量的入度
int head[2021];//一开始的图
int head2[2021];//保存缩点后新的有向无环图
int stk[2021], top;//数组模拟栈,top记录栈中元素个数
int sze[2021];//记录每个强连通分量中元素个数
bitset<150021> f[100021];
queue<int> q;
struct edge{
    int u, v;
}no[2021*2021], no2[2021*2021];
void add(int from, int to) {
    no[++cnt].u = to;
    no[cnt].v = head[from];
    head[from] = cnt;
}
void add2(int from, int to) {
    no2[++cnt].u = to;
    no[cnt].v = head[from];
    head2[from] = cnt;
}
void tarjan(int now) {
    dfn[now] = low[now] = ++sum;//初始化为自己,时间戳++
    stk[++top] = now;//入栈
    v[now] = true;//标记为已访问过
    for (int i = head[now]; i; i = no[i].v) {
        int to = no[i].u;
        if (!dfn[to]) {//如果该点没被访问过,则该边可以加到搜索树中
            tarjan(to);
            low[now] = min(low[now], low[to]);
        } else if (v[to]) {//如果以被访问过,则该边不能被加到搜索树中
            low[now] = min(low[now], dfn[to]);
        }
    }
    if (low[now] == dfn[now]) {// 如果节点now是强连通分量的根
                               //则以该节点为根的子树中所有节点不可能与栈中元素构成环
                               //此时从now到栈定的所有节点构成一个强连通分量
        int y;
        tot++;//强连通分量个数++
        do {
            y = stk[top--];//弹出栈顶元素,为一个强连通分量的一个元素
            scc[y] = tot;//记录编号,该元素属于编号为tot的强连通分量
            sze[tot]++;//该连通块中元素++
            v[y] = false;
        } while (y != now);//直到该节点为止
    }
}
int main () {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            int x;
            scanf("%1d", &x);
            if (x == 1) {
                add(i, j);
            }
        }
    }
    cnt = 0;
    for (int i = 1; i <= n; i++) {
        if (!dfn[i]) {
            tarjan(i);
        }
    }
    for (int x = 1; x <= n; x++) {//缩点
        for (int i = head[x]; i; i = no[i].v) {
            int to = no[i].u;
            if (scc[x] == scc[to]) {//共属同一强连通分量
                continue;
            }
            deg[scc[x]]++;// scc[x] 表示的强连通分量入度++
            add2(scc[to], scc[x]);
        }
    }
    for (int i = 1; i <= tot; i++) {
        f[i][i] = 1;//自己到自己也算联通
    }
    for (int i = 1; i <= tot; i++) {//拓扑排序
        if (!deg[i]) {//说明 i 入度为0
            q.push(i);//入队,将其分离
        }
    }
    while (q.size()) {//拓扑排序,直到所有点被分离出来
        int u = q.front();
        q.pop();
        for (int i = head2[u]; i; i = no2[i].v) {
            int to = no2[i].u;
            deg[to]--;//该点指向的点入度--
            f[to] |= f[u];//或运算累加
            if (!deg[to]) {
                q.push(to);
            }
        }
    }
    for (int i = 1; i <= tot; i++) {
        for (int j = 1; j <= tot; j++) {
            if (f[i][j]) {//bitset表示两强连通分量是否联通
                ans += sze[i] * sze[j];//个数相乘
            }
        }
    }
    printf("%d\n", ans);
    return 0;
}

/*   bitset 做法   B君的
#include <bits/stdc++.h>
using namespace std;
bitset<2000>d[2000];
char s[2020];
int n, z;
int main() {
    scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        scanf("%s", s);
        for (int j = 0; j < n; j++) {
            if (s[j] == '1') {
                d[i][j] = 1;
            }
        }
        d[i][i] = 1;
    }
    for (int k = 0; k < n; k++) {
        for (int i = 0; i < n; i++) {
            if (d[i][k]) {
                d[i] |= d[k];
            }
        }
    }
    for (int i = 0; i < n; i++) {
        z += d[i].count();
    }
    printf("%d\n", z);
}
*/

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!