连通数

LuoguP4306    bzoj2208

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

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

Code:

 

  1 #include <bits/stdc++.h>
  2 using namespace std;
  3 int n, ans, cnt, sum, tot;//tot记录图中有几个强连通分量
  4 bool v[2021];//判断节点是否被访问过
  5 int dfn[2021];//节点i搜索的次序编号(时间戳)每个点第一次被访问的时间顺序
  6 int low[2021];//表示u或u的子树能够追溯到的最早的栈中节点的次序号,时间戳的最小值
  7 int scc[2021];//表示 x 所在的强连通分量的编号
  8 int deg[2021];//储存强连通分量的入度
  9 int head[2021];//一开始的图
 10 int head2[2021];//保存缩点后新的有向无环图
 11 int stk[2021], top;//数组模拟栈,top记录栈中元素个数
 12 int sze[2021];//记录每个强连通分量中元素个数
 13 bitset<150021> f[100021];
 14 queue<int> q;
 15 struct edge{
 16     int u, v;
 17 }no[2021*2021], no2[2021*2021];
 18 void add(int from, int to) {
 19     no[++cnt].u = to;
 20     no[cnt].v = head[from];
 21     head[from] = cnt;
 22 }
 23 void add2(int from, int to) {
 24     no2[++cnt].u = to;
 25     no[cnt].v = head[from];
 26     head2[from] = cnt;
 27 }
 28 void tarjan(int now) {
 29     dfn[now] = low[now] = ++sum;//初始化为自己,时间戳++
 30     stk[++top] = now;//入栈
 31     v[now] = true;//标记为已访问过
 32     for (int i = head[now]; i; i = no[i].v) {
 33         int to = no[i].u;
 34         if (!dfn[to]) {//如果该点没被访问过,则该边可以加到搜索树中
 35             tarjan(to);
 36             low[now] = min(low[now], low[to]);
 37         } else if (v[to]) {//如果以被访问过,则该边不能被加到搜索树中
 38             low[now] = min(low[now], dfn[to]);
 39         }
 40     }
 41     if (low[now] == dfn[now]) {// 如果节点now是强连通分量的根
 42                                //则以该节点为根的子树中所有节点不可能与栈中元素构成环
 43                                //此时从now到栈定的所有节点构成一个强连通分量
 44         int y;
 45         tot++;//强连通分量个数++
 46         do {
 47             y = stk[top--];//弹出栈顶元素,为一个强连通分量的一个元素
 48             scc[y] = tot;//记录编号,该元素属于编号为tot的强连通分量
 49             sze[tot]++;//该连通块中元素++
 50             v[y] = false;
 51         } while (y != now);//直到该节点为止
 52     }
 53 }
 54 int main () {
 55     scanf("%d", &n);
 56     for (int i = 1; i <= n; i++) {
 57         for (int j = 1; j <= n; j++) {
 58             int x;
 59             scanf("%1d", &x);
 60             if (x == 1) {
 61                 add(i, j);
 62             }
 63         }
 64     }
 65     cnt = 0;
 66     for (int i = 1; i <= n; i++) {
 67         if (!dfn[i]) {
 68             tarjan(i);
 69         }
 70     }
 71     for (int x = 1; x <= n; x++) {//缩点
 72         for (int i = head[x]; i; i = no[i].v) {
 73             int to = no[i].u;
 74             if (scc[x] == scc[to]) {//共属同一强连通分量
 75                 continue;
 76             }
 77             deg[scc[x]]++;// scc[x] 表示的强连通分量入度++
 78             add2(scc[to], scc[x]);
 79         }
 80     }
 81     for (int i = 1; i <= tot; i++) {
 82         f[i][i] = 1;//自己到自己也算联通
 83     }
 84     for (int i = 1; i <= tot; i++) {//拓扑排序
 85         if (!deg[i]) {//说明 i 入度为0
 86             q.push(i);//入队,将其分离
 87         }
 88     }
 89     while (q.size()) {//拓扑排序,直到所有点被分离出来
 90         int u = q.front();
 91         q.pop();
 92         for (int i = head2[u]; i; i = no2[i].v) {
 93             int to = no2[i].u;
 94             deg[to]--;//该点指向的点入度--
 95             f[to] |= f[u];//或运算累加
 96             if (!deg[to]) {
 97                 q.push(to);
 98             }
 99         }
100     }
101     for (int i = 1; i <= tot; i++) {
102         for (int j = 1; j <= tot; j++) {
103             if (f[i][j]) {//bitset表示两强连通分量是否联通
104                 ans += sze[i] * sze[j];//个数相乘
105             }
106         }
107     }
108     printf("%d\n", ans);
109     return 0;
110 }
111 
112 /*   bitset 做法   B君的
113 #include <bits/stdc++.h>
114 using namespace std;
115 bitset<2000>d[2000];
116 char s[2020];
117 int n, z;
118 int main() {
119     scanf("%d", &n);
120     for (int i = 0; i < n; i++) {
121         scanf("%s", s);
122         for (int j = 0; j < n; j++) {
123             if (s[j] == '1') {
124                 d[i][j] = 1;
125             }
126         }
127         d[i][i] = 1;
128     }
129     for (int k = 0; k < n; k++) {
130         for (int i = 0; i < n; i++) {
131             if (d[i][k]) {
132                 d[i] |= d[k];
133             }
134         }
135     }
136     for (int i = 0; i < n; i++) {
137         z += d[i].count();
138     }
139     printf("%d\n", z);
140 }
141 */
View Code

 

posted @ 2019-11-10 15:22  Sun-dial  阅读(192)  评论(0编辑  收藏  举报