连通数
这道题里面还有缩点+拓扑排序
话不多说直接上代码,代码里有详细解释,看不懂的先去看看搜索树方面的知识,lyd书上有
Code:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 */