Tarjan 联通图 Kuangbin 带你飞 联通图题目及部分联通图题目
Tarjan算法就不说了
想学看这
https://www.byvoid.com/blog/scc-tarjan/
https://www.byvoid.com/blog/biconnect/
下面是几份基本的模版
首先是无向图割点桥的代码
下面的代码是用于求割点数目的
其中add_block[u] = x 表示删除u点之后增加的联通块个数。注意是增加的联通块个数
const int MAXN = 1010; const int MAXM = 10010; const int INF = 0x3f3f3f3f; struct Edge { int u,v,next; int w; bool cut; }edge[MAXN]; int head[MAXN],tot; int Low[MAXN],DFN[MAXN],Stack[MAXN]; int Index,top; bool Instack[MAXN]; bool cut[MAXN]; int add_block[MAXN]; int N,bridge; void init() { tot = 0; memset(head,-1,sizeof(head)); } void add_edge(int u,int v,int w) { edge[tot].u = u; edge[tot].v = v; edge[tot].w = w; edge[tot].next = head[u]; head[u] = tot++; } void Tarjan(int u,int pre) { int v; Low[u] = DFN[u] = ++Index; Stack[top++] = u; Instack[u] = true; int son = 0; for (int i = head[u] ; i != -1 ; i = edge[i].next) { v = edge[i].v; if (v == pre) continue; if (!DFN[v]) { son++; Tarjan(v,u); if (Low[u] > Low[v]) Low[u] = Low[v]; if (Low[u] > DFN[v]) { bridge++; edge[i].cut = true; edge[i ^ 1].cut = true; } if (u != pre && Low[v] >= DFN[u]) { cut[u] = true; add_block[u]++; } } else if (Low[u] > DFN[v]) Low[u] = DFN[v]; } if (u == pre && son > 1) cut[u] = true; if (u == pre) add_block[u] = son - 1; Instack[u] = false; top--; } void slove() { memset(DFN,0,sizeof(DFN)); memset(Instack,false,sizeof(Instack)); memset(cut,false,sizeof(cut)); memset(add_block,0,sizeof(add_block)); top = Index = 0; bridge = 0; for (int i = 1 ; i <= N ; i++) if (!DFN[i]) Tarjan(i,i); int ret = 0; for (int i = 1 ; i <= N ; i++) if (cut[i]) ret++; printf("%d\n",ret); }
下面的代码仍然是无向图模版。适用于缩点存储。
其中联通块编号为1-bcc。Belong[u] = x;
表示u位于第x个联通块
const int MAXN = 100010; const int MAXM = 400010; int N,M; struct Edge { int u,v,w; int next; // int id; bool cut; }edge[MAXM]; int head[MAXN],tot; int Low[MAXN],DFN[MAXN],Stack[MAXN],Belong[MAXN]; int Index,top; int block; bool Instack[MAXN]; int bridge; void init() { tot = 0; memset(head,-1,sizeof(head)); } void add_edge(int u,int v,int w,int id = 9797) { edge[tot].u = u; edge[tot].v = v; // edge[tot].id = id; edge[tot].cut = false; edge[tot].next = head[u]; head[u] =tot++; } void Tarjan(int u,int pre) { int v; Low[u] = DFN[u] = ++Index; Instack[u] = true; Stack[top++]= u; for (int i = head[u] ; i != -1 ; i = edge[i].next) { int v = edge[i].v; if (v == pre) continue; if (!DFN[v]) { Tarjan(v,u); if (Low[u] > Low[v]) Low[u] = Low[v]; if (Low[v] > DFN[u]) { bridge++; edge[i].cut = true; edge[i ^ 1].cut = true; } } else if (Instack[v] && Low[u] > DFN[v]) Low[u] = DFN[v]; } if (Low[u] == DFN[u]) { block++; do { v = Stack[--top]; Instack[v] = false; Belong[v] = block; }while (u != v); } } int deg[MAXN]; void calcu(int N) { memset(Instack,false,sizeof(Instack)); memset(DFN,0,sizeof(DFN)); Index = top = bridge = block = 0 ; for (int i = 1 ; i <= N ; i++) { if (!DFN[i]) Tarjan(i,i); } int ret = 0; memset(deg,0,sizeof(deg)); for (int u = 1 ; u <= N; u++) { for (int i = head[u]; i != -1 ; i = edge[i].next) { if (edge[i].cut == false) continue; //int v = edge[i].v; deg[Belong[u]]++; } } for (int i = 1 ; i <= block ; i++) if (deg[i] == 1) ret++; printf("%d\n",(ret + 1) / 2); }
下面是在含有重边图中求桥。这里特殊说明一下
正常的tarjan求桥的dfs有两个参数x和fa,其中参数fa就是用于判定某边(x,t)是不是刚刚走过来的边.
在有重边的情形下,这样判就不太方便了...
于是我们修改这个判定条件,把参数fa改为刚刚走过来的这条边的编号.
这就没问题了.至于具体的实现,由于一条无向边拆成两条有向边存下来的时候,编号是连续的,因此像类似4,5这样的两条边,可以通过判断4/2==5/2来判定是否是由同一条无向边拆出来的。
比如下图
该图是没有桥的。然而用以上的版来求就会有问题。下面是正确的求法
边结构体里的id注意一条无向边的2个分开有向边的编号一样
Edge edge[MAXM]; int head[MAXN],tot; int Low[MAXN],DFN[MAXN],Stack[MAXN]; int Index,top; bool Instack[MAXN]; bool cut[MAXN]; int add_block[MAXN]; int bridge; void init() { memset(head,-1,sizeof(head)); tot = 0; } void add_edge(int u,int v,LL w,int id) { edge[tot].u = u; edge[tot].v = v; edge[tot].w = w; edge[tot].id = id; edge[tot].next = head[u]; edge[tot].cut = false; head[u] = tot++; } void Tarjan(int u,int pre) { int v; Low[u] = DFN[u] = ++Index; Stack[top++] = u; Instack[u] = true; int son = 0; for (int i = head[u] ; i != -1 ; i = edge[i].next) { v = edge[i].v; if (!DFN[v]) { son++; Tarjan(v,edge[i].id); if (Low[u] > Low[v]) Low[u] = Low[v]; if (Low[v] > DFN[u]) { bridge++; edge[i].cut = true; edge[i ^ 1].cut = true; } if (pre != -1 && Low[v] >= DFN[u]) { cut[u] = true; add_block[u]++; } } else if (DFN[v] < DFN[u] && edge[i].id != pre) Low[u] = min(Low[u],DFN[v]); } /* if (Low[u] == DFN[u]) { block++; do { v = Stack[--top]; Instack[v] = true; Belong[v] = block; }while (v != u); } */ if (pre == -1 && son > 1) cut[u] = true; if (pre == -1) add_block[u] = son - 1; Instack[u] = false; top--; }
接下来是有向图强联通的相关模版。强联通分量标号从1-scc
其中num[x] = i 表示x号强联通分量的点的个数为i
const int MAXN = 1010; const int MAXM = MAXN * MAXN; const int INF = 0x3f3f3f3f; struct Edge { int u,v,next; int w; }edge[MAXM]; int N; int head[MAXN],tot; int Low[MAXN],DFN[MAXN],Stack[MAXN],Belong[MAXN]; int Index,top; int scc; bool Instack[MAXN]; int degin[MAXN],degout[MAXN]; int num[MAXN]; void init() { tot = 0; memset(head,-1,sizeof(head)); } void add_edge(int u,int v,int w) { edge[tot].u = u; edge[tot].v = v; edge[tot].w = w; edge[tot].next = head[u]; head[u] = tot++; } void Tarjan(int u) { int v; Low[u] = DFN[u] = ++Index; Stack[top++] = u; Instack[u] = true; for (int i = head[u] ; i != -1 ; i = edge[i].next) { int v = edge[i].v; if (!DFN[v]) { Tarjan(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 = Stack[--top]; Instack[v] = false; Belong[v] = scc; num[scc]++; }while (v != u); } } void slove() { memset(DFN,0,sizeof(DFN)); memset(Instack,false,sizeof(Instack)); memset(num,0,sizeof(num)); memset(degin,0,sizeof(degin)); memset(degout,0,sizeof(degout)); Index = top = 0; scc = 0; for (int i = 1 ; i <= N ; i++) if (!DFN[i]) Tarjan(i); for (int i = 0 ; i < tot ; i++) { int u = Belong[edge[i].u]; int v = Belong[edge[i].v]; if (u == v) continue; degout[u]++; degin[v]++; } //for (int i = 1 ; i <= N ; i++) printf("%d ",Belong[i]); putchar('\n'); int ansl = 0,ansr = 0; for (int i = 1 ; i <= scc ; i++) if (degin[i] == 0) ansl++; else if (degout[i] == 0) ansr++; int ret = max(ansl,ansr); if (scc == 1) ret = 0; printf("%d\n%d\n",ansl,ret); }
下面是全部Kuangbin联通图的几题的题解
POJ 1236 Network of Schools
题目大意:有N个学校,从每个学校都能从一个单向网络到另外一个学校,两个问题
1:初始至少需要向多少个学校发放软件,使得网络内所有的学校最终都能得到软件。
2:至少需要添加几条边,使任意向一个学校发放软件后,经过若干次传送,网络内所有的学校最终都能得到软件。
首先第一问就是有多少个强联通分量的入度为0;
第二问就是max(出度为0的点的个数,入度为0的点的个数)
第一问很容易发现答案。第二问原因首先就是缩点之后相当于形成了一棵树。最优的肯定是出度为0的叶子节点和入度为0的
跟相连最优。则max就是答案。下面代码
#include <map> #include <set> #include <list> #include <cmath> #include <ctime> #include <deque> #include <stack> #include <queue> #include <cctype> #include <cstdio> #include <string> #include <vector> #include <climits> #include <cstdlib> #include <cstring> #include <iostream> #include <algorithm> #define LL long long #define PI 3.1415926535897932626 using namespace std; int gcd(int a, int b) {return a % b == 0 ? b : gcd(b, a % b);} const int MAXN = 1010; const int MAXM = MAXN * MAXN; const int INF = 0x3f3f3f3f; struct Edge { int u,v,next; int w; }edge[MAXM]; int N; int head[MAXN],tot; int Low[MAXN],DFN[MAXN],Stack[MAXN],Belong[MAXN]; int Index,top; int scc; bool Instack[MAXN]; int degin[MAXN],degout[MAXN]; int num[MAXN]; void init() { tot = 0; memset(head,-1,sizeof(head)); } void add_edge(int u,int v,int w) { edge[tot].u = u; edge[tot].v = v; edge[tot].w = w; edge[tot].next = head[u]; head[u] = tot++; } void Tarjan(int u) { int v; Low[u] = DFN[u] = ++Index; Stack[top++] = u; Instack[u] = true; for (int i = head[u] ; i != -1 ; i = edge[i].next) { int v = edge[i].v; if (!DFN[v]) { Tarjan(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 = Stack[--top]; Instack[v] = false; Belong[v] = scc; num[scc]++; }while (v != u); } } void slove(int n) { memset(DFN,0,sizeof(DFN)); memset(Instack,false,sizeof(Instack)); memset(num,0,sizeof(num)); memset(degin,0,sizeof(degin)); memset(degout,0,sizeof(degout)); Index = top = 0; scc = 0; for (int i = 1 ; i <= N ; i++) if (!DFN[i]) Tarjan(i); for (int i = 0 ; i < tot ; i++) { int u = Belong[edge[i].u]; int v = Belong[edge[i].v]; if (u == v) continue; degout[u]++; degin[v]++; } //for (int i = 1 ; i <= N ; i++) printf("%d ",Belong[i]); putchar('\n'); int ansl = 0,ansr = 0; for (int i = 1 ; i <= scc ; i++) if (degin[i] == 0) ansl++; else if (degout[i] == 0) ansr++; int ret = max(ansl,ansr); if (scc == 1) ret = 0; printf("%d\n%d\n",ansl,ret); } int main() { while (scanf("%d",&N) != EOF) { init(); for (int i = 1 ; i <= N ; i++) { int x; while (scanf("%d",&x) != EOF) { if (x == 0) break; add_edge(i,x,9797); } } slove(N); } return 0; }
Uva 315 Network 求割点个数
#include <map> #include <set> #include <list> #include <cmath> #include <ctime> #include <deque> #include <stack> #include <queue> #include <cctype> #include <cstdio> #include <string> #include <vector> #include <climits> #include <cstdlib> #include <cstring> #include <iostream> #include <algorithm> #define LL long long #define PI 3.1415926535897932626 using namespace std; int gcd(int a, int b) {return a % b == 0 ? b : gcd(b, a % b);} const int MAXN = 1010; const int MAXM = 10010; const int INF = 0x3f3f3f3f; struct Edge { int u,v,next; int w; bool cut; }edge[MAXN]; int head[MAXN],tot; int Low[MAXN],DFN[MAXN],Stack[MAXN]; int Index,top; bool Instack[MAXN]; bool cut[MAXN]; int add_block[MAXN]; int N,bridge; void init() { tot = 0; memset(head,-1,sizeof(head)); } void add_edge(int u,int v,int w) { edge[tot].u = u; edge[tot].v = v; edge[tot].w = w; edge[tot].next = head[u]; head[u] = tot++; } void Tarjan(int u,int pre) { int v; Low[u] = DFN[u] = ++Index; Stack[top++] = u; Instack[u] = true; int son = 0; for (int i = head[u] ; i != -1 ; i = edge[i].next) { v = edge[i].v; if (v == pre) continue; if (!DFN[v]) { son++; Tarjan(v,u); if (Low[u] > Low[v]) Low[u] = Low[v]; if (Low[u] > DFN[v]) { bridge++; edge[i].cut = true; edge[i ^ 1].cut = true; } if (u != pre && Low[v] >= DFN[u]) { cut[u] = true; add_block[u]++; } } else if (Low[u] > DFN[v]) Low[u] = DFN[v]; } if (u == pre && son > 1) cut[u] = true; if (u == pre) add_block[u] = son - 1; Instack[u] = false; top--; } void slove() { memset(DFN,0,sizeof(DFN)); memset(Instack,false,sizeof(Instack)); memset(cut,false,sizeof(cut)); memset(add_block,0,sizeof(add_block)); top = Index = 0; bridge = 0; for (int i = 1 ; i <= N ; i++) if (!DFN[i]) Tarjan(i,i); int ret = 0; for (int i = 1 ; i <= N ; i++) if (cut[i]) ret++; printf("%d\n",ret); } int main() { while (scanf("%d",&N) != EOF) { if (N == 0) break; init(); int u,v; while (scanf("%d",&u) != EOF) { if (u == 0) break; char ch; while (scanf("%d%c",&v,&ch) != EOF) { add_edge(u,v,9797); add_edge(v,u,9797); if (ch == '\n') break; } } slove(); } return 0; }
POJ 3694 Newwork
有一个可重边的无向图。Q个询问每次向其加边。输出每次加边之后的无向图桥的个数
首先直接干是应该不能过的。不过听说数据很水可以过。。
做法:首先缩点形成一个树。于是树上的所有边就是桥,那么每次加边首先判断是不是在一个双联通里
如果在那么不需要更新直接输出桥即可。否则两边在树上向上找LCA即可。路过的树边如果之前没有标记为不是桥
那么就更新答案。这里我自己写的无穷WA。至今不知为何。学了Kuangbin神的写法。这个代码不是标记边
而是标记桥的终点是否已经被删除,代码就看把。LCA并不是用很高深的方法求的(实际我不会)
就一层一层的向上找
#include <map> #include <set> #include <list> #include <cmath> #include <ctime> #include <deque> #include <stack> #include <queue> #include <cctype> #include <cstdio> #include <string> #include <vector> #include <climits> #include <cstdlib> #include <cstring> #include <iostream> #include <algorithm> #define LL long long #define PI 3.1415926535897932626 using namespace std; int gcd(int a, int b) {return a % b == 0 ? b : gcd(b, a % b);} const int MAXN = 100010; const int MAXM = 400010; struct Edge { int u,v,w; int next; int id; bool cut; }edge[MAXM]; int head[MAXN],tot; int N,M; int Low[MAXN],DFN[MAXN],Stack[MAXN],Belong[MAXN]; int Index,top; int block; bool Instack[MAXN]; int bridge; void init() { tot = 0; memset(head,-1,sizeof(head)); } void add_edge(int u,int v,int w,int id) { edge[tot].u = u; edge[tot].v = v; edge[tot].id = id; edge[tot].cut = false; edge[tot].next = head[u]; head[u] =tot++; } void Tarjan(int u,int pre) { int v; Low[u] = DFN[u] = ++Index; Stack[top++] = u; Instack[u] = true; int son = 0; for (int i = head[u] ; i != -1 ; i = edge[i].next) { v = edge[i].v; if (!DFN[v]) { son++; Tarjan(v,edge[i].id); if (Low[u] > Low[v]) Low[u] = Low[v]; if (Low[v] > DFN[u]) { bridge++; edge[i].cut = true; edge[i ^ 1].cut = true; } } else if (DFN[v] < DFN[u] && edge[i].id != pre) Low[u] = min(Low[u],DFN[v]); } if (Low[u] == DFN[u]) { block++; do { v = Stack[--top]; Instack[v] = true; Belong[v] = block; }while (v != u); } } vector<int>vec[MAXN]; int father[MAXN],dep[MAXN],a[MAXN]; void LCA_bfs(int root) { memset(dep,-1,sizeof(dep)); dep[root] = 0; a[root] = 0; father[root] = -1; queue<int>q; q.push(root); while (!q.empty()) { int u = q.front(); q.pop(); for (int i = 0 ; i < (int)vec[u].size() ; i++) { int v = vec[u][i]; if (dep[v] != -1) continue; dep[v] = dep[u] + 1; a[v] = 1; father[v] = u; q.push(v); } } } int ret; void lca(int u,int v) { if (dep[u] > dep[v]) swap(u,v); while (dep[u] < dep[v]) { if (a[v]) { ret--; a[v] = 0; } v = father[v]; } while (u != v) { if (a[u]) { ret--; a[u] = 0; } if (a[v]) { ret--; a[v] = 0; } u = father[u]; v = father[v]; } } void calcu(int N) { memset(Instack,false,sizeof(Instack)); memset(DFN,0,sizeof(DFN)); Index = top = block = 0; bridge = 0; for (int i = 1 ; i <= N ; i++) if (!DFN[i]) Tarjan(i,-1); for (int i = 1 ; i <= N ; i++) vec[i].clear(); for (int u = 1 ; u <= N ; u++) { for (int i = head[u] ; i != -1 ; i = edge[i].next) { if (edge[i].cut == false) continue; int v = edge[i].v; vec[Belong[u]].push_back(Belong[v]); vec[Belong[v]].push_back(Belong[u]); } } // printf("%d\n",bridge); LCA_bfs(1); ret = block - 1; int Q; scanf("%d",&Q); while (Q--) { int u,v; scanf("%d%d",&u,&v); lca(Belong[u],Belong[v]); printf("%d\n",ret); } putchar('\n'); } int main() { // freopen("sample.txt","r",stdin); int kase = 1; while (scanf("%d%d",&N,&M) != EOF) { if (N == 0 && M == 0) break; printf("Case %d:\n",kase++); init(); for (int i = 0 ; i < M ; i++) { int u,v; scanf("%d%d",&u,&v); add_edge(u,v,9797,i); add_edge(v,u,9797,i); } calcu(N); } return 0; }
POJ 3177 Redundant Paths
题意:最少添加几条边使得全图双联通即无桥
做法:缩点形成树。直接所有叶子节点之间相互连边肯定是最优的。那么ans=(leaf + 1) / 2;
#include <map> #include <set> #include <list> #include <cmath> #include <ctime> #include <deque> #include <stack> #include <queue> #include <cctype> #include <cstdio> #include <string> #include <vector> #include <climits> #include <cstdlib> #include <cstring> #include <iostream> #include <algorithm> #define LL long long #define PI 3.1415926535897932626 using namespace std; int gcd(int a, int b) {return a % b == 0 ? b : gcd(b, a % b);} const int MAXN = 100010; const int MAXM = 400010; int N,M; struct Edge { int u,v,w; int next; // int id; bool cut; }edge[MAXM]; int head[MAXN],tot; int Low[MAXN],DFN[MAXN],Stack[MAXN],Belong[MAXN]; int Index,top; int block; bool Instack[MAXN]; int bridge; void init() { tot = 0; memset(head,-1,sizeof(head)); } void add_edge(int u,int v,int w,int id = 9797) { edge[tot].u = u; edge[tot].v = v; // edge[tot].id = id; edge[tot].cut = false; edge[tot].next = head[u]; head[u] =tot++; } void Tarjan(int u,int pre) { int v; Low[u] = DFN[u] = ++Index; Instack[u] = true; Stack[top++]= u; for (int i = head[u] ; i != -1 ; i = edge[i].next) { int v = edge[i].v; if (v == pre) continue; if (!DFN[v]) { Tarjan(v,u); if (Low[u] > Low[v]) Low[u] = Low[v]; if (Low[v] > DFN[u]) { bridge++; edge[i].cut = true; edge[i ^ 1].cut = true; } } else if (Instack[v] && Low[u] > DFN[v]) Low[u] = DFN[v]; } if (Low[u] == DFN[u]) { block++; do { v = Stack[--top]; Instack[v] = false; Belong[v] = block; }while (u != v); } } int deg[MAXN]; void calcu(int N) { memset(Instack,false,sizeof(Instack)); memset(DFN,0,sizeof(DFN)); Index = top = bridge = block = 0 ; for (int i = 1 ; i <= N ; i++) { if (!DFN[i]) Tarjan(i,i); } int ret = 0; memset(deg,0,sizeof(deg)); for (int u = 1 ; u <= N; u++) { for (int i = head[u]; i != -1 ; i = edge[i].next) { if (edge[i].cut == false) continue; //int v = edge[i].v; deg[Belong[u]]++; } } for (int i = 1 ; i <= block ; i++) if (deg[i] == 1) ret++; printf("%d\n",(ret + 1) / 2); } int main() { while (scanf("%d%d",&N,&M) != EOF) { init(); while (M--) { int u,v; scanf("%d%d",&u,&v); add_edge(u,v,9797); add_edge(v,u,9797); } calcu(N); } return 0; }
HDU 4612 Warm up
加一条边使得加这条边的新图的桥最少。
首先由上一题POJ 3177 肯定知道缩点后连叶子节点肯定更优。问题就变成了找哪2个叶子节点连边
第一次遇到这个问题的时候是gym 100002 记得。当时我的做法就暴力枚举叶子节点N*N,然后倍增找的最优方案
复杂度不过。后来发现最优的就是这个缩点形成的树的最长链,这条最长链在树边全部是桥的树上删除的树是最多的
那么就是如何找最长连。很简单。2次DFS,第一次DFS,直接找最深的点,这个点记为u。那么接下来在一次DFS,从
u DFS找到最深(无向图会回到树根然后再找另一个最深)这个深度就处理出了删除桥的个数
另外本题要手工扩栈
#pragma comment(linker, "/STACK:1024000000,1024000000") #include <map> #include <set> #include <list> #include <cmath> #include <ctime> #include <deque> #include <stack> #include <queue> #include <cctype> #include <cstdio> #include <string> #include <vector> #include <climits> #include <cstdlib> #include <cstring> #include <iostream> #include <algorithm> #define LL long long #define PI 3.1415926535897932626 using namespace std; int gcd(int a, int b) {return a % b == 0 ? b : gcd(b, a % b);} inline int readint() { char c = getchar(); while(!isdigit(c)) c = getchar(); int x = 0; while(isdigit(c)) { x = x * 10 + c - '0'; c = getchar(); } return x; } const int MAXN = 200010; const int MAXM = MAXN * 10; const int INF = 0x3f3f3f3f; struct Edge { int u,v,w; int next; int id; bool cut; }edge[MAXM]; int head[MAXN],tot; int Low[MAXN],DFN[MAXN],Stack[MAXN],Belong[MAXN]; int Index,top,block,bridge; bool vis[MAXN]; bool Instack[MAXN]; int N,M; void init() { memset(head,-1,sizeof(head)); tot = 0; } void add_edge(int u,int v,int w,int id) { edge[tot].u = u; edge[tot].v = v; edge[tot].w = w; edge[tot].id = id; edge[tot].cut = false; edge[tot].next = head[u]; head[u] = tot++; } void Tarjan(int u,int pre) { int v; Low[u] = DFN[u] = ++Index; Stack[top++] = u; Instack[u] = true; int son = 0; for (int i = head[u] ; i != -1 ; i = edge[i].next) { v = edge[i].v; if (!DFN[v]) { son++; Tarjan(v,edge[i].id); if (Low[u] > Low[v]) Low[u] = Low[v]; if (Low[v] > DFN[u]) { bridge++; edge[i].cut = true; edge[i ^ 1].cut = true; } } else if (DFN[v] < DFN[u] && edge[i].id != pre) Low[u] = min(Low[u],DFN[v]); } if (Low[u] == DFN[u]) { block++; do { v = Stack[--top]; Instack[v] = true; Belong[v] = block; }while (v != u); } } int pos,maxdep,ret; vector<int>vec[MAXN]; void dfs(int cur,int fa,int curdep) { vis[cur] = true; if (curdep > maxdep) { maxdep = curdep; pos = cur; } for (int i = 0 ; i < (int)vec[cur].size() ; i++) { int v = vec[cur][i]; if (vis[v] || v == fa) continue; dfs(v,cur,curdep + 1); } } void calcu(int N) { for (int i = 0 ; i <= N ; i++) Instack[i] = false; for (int i = 1 ; i <= N ; i++) DFN[i] = 0; Index = top = bridge = block = 0; for (int i = 1 ; i <= N ; i++) { if (!DFN[i]) Tarjan(i,-1); } for (int i = 0 ; i <= block ; i++) vec[i].clear(); for (int u = 1 ; u <= N ; u++) { for (int i = head[u] ; i != -1 ; i = edge[i].next) { if (edge[i].cut == false) continue; int u = Belong[edge[i].u]; int v = Belong[edge[i].v]; vec[u].push_back(v); vec[v].push_back(u); } } maxdep = 0; for (int i = 0 ; i < block + 10 ; i++) vis[i] = false; dfs(1,-1,0); ret = 0; for (int i = 0 ; i < block + 10 ; i++) vis[i] = false; maxdep = 0; dfs(pos,-1,0); printf("%d\n",bridge - maxdep); } int main() { while (scanf("%d%d",&N,&M) != EOF) { if (N == 0 && M == 0) break; init(); for (int i = 1 ; i <= M ; i++) { int u,v; u = readint(); v = readint(); add_edge(u,v,9797,i); add_edge(v,u,9797,i); } calcu(N); } return 0; }
HDU 4635 Strongly connected
题意:最多添加几条边使得图仍然不是强联通的。还有一些其他情况,具体读题把!
这题很厉害啊;
首先我们可以分析。加完边之后的图是什么样子的
首先其必然不是强联通的。要加尽量多的边,相当与分成2个部分我们另它们分别为U,V;
另外我们知道如果边尽量多且不是双联通。那么就全部U联通分量的点都连出一条有向边到V;
U内强联通且V内强联通便最多。这时候的边的数目就是num[u] * num[v] + num[u] * (num[u] - 1) +
num[v] * (num[v] - 1);
这样问题转换成了
在这样的图中和起始给与边M的最大差,下面我分析下上面那个式子
由均值不等式且numu + numv == N(点数)是必然的那么我定义U部点个数为x,V部点个数为y
就有x + y = N;
最后的边的个数是x * y + x * (x - 1) + y * (y - 1) = x * y + x * x * y * y - x - y =
(x + y) ^ 2 - xy - x - y = N^2 - N - xy;到了这里就能发现x与y差距越大那么边数越大
假设只有入度或者出度为0的才可能成为U部,V部。那么代码就如下
#include <map> #include <set> #include <list> #include <cmath> #include <ctime> #include <deque> #include <stack> #include <queue> #include <cctype> #include <cstdio> #include <string> #include <vector> #include <climits> #include <cstdlib> #include <cstring> #include <iostream> #include <algorithm> #define LL long long #define PI 3.1415926535897932626 using namespace std; int gcd(int a, int b) {return a % b == 0 ? b : gcd(b, a % b);} const int MAXN = 100010; const int MAXM = 100010; struct Edge { int u,v,next; int w; }edge[MAXM]; int head[MAXN],tot; LL Low[MAXN],DFN[MAXN],Stack[MAXN],Belong[MAXN]; int Index,top; int scc; LL N,M; bool Instack[MAXN]; LL num[MAXN]; void init() { tot = 0; memset(head,-1,sizeof(head)); } void add_edge(int u,int v,int w) { edge[tot].u = u; edge[tot].v = v; edge[tot].w = w; edge[tot].next = head[u]; head[u] = tot++; } void Tarjan(int u) { int v; Low[u] = DFN[u] = ++Index; Stack[top++] = u; Instack[u] = true; for (int i = head[u] ; i != -1 ; i = edge[i].next) { v = edge[i].v; if (!DFN[v]) { Tarjan(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 = Stack[--top]; Instack[v] = false; Belong[v] = scc; num[scc]++; }while (v != u); } } LL degin[MAXN]; LL degout[MAXN]; void calcu(LL N,int kase) { memset(DFN,0,sizeof(DFN)); memset(Instack,false,sizeof(Instack)); memset(num,0,sizeof(num)); memset(degin,0,sizeof(degin)); memset(degout,0,sizeof(degout)); Index = scc = top = 0; for (int i = 1 ; i <= N ; i++) if (!DFN[i]) Tarjan(i); if (scc == 1) { printf("Case %d: -1\n",kase); return; } for (int i = 0 ; i < tot ; i++) { int u = Belong[edge[i].u]; int v = Belong[edge[i].v]; if (u == v) continue; degin[v]++; degout[u]++; } LL tmp = N * (N - 1) - M; LL ret = 0; for (int i = 1 ; i <= scc ; i++) { if (degin[i] == 0 || degout[i] == 0) { ret = max(ret,(N - num[i]) * num[i] +(num[i] - 1) * num[i] + (N - num[i] - 1) * (N - num[i]) - M); } } printf("Case %d: %d\n",kase,ret); } int main() { int T,kase = 1; scanf("%d",&T); while (T--) { init(); scanf("%I64d%I64d",&N,&M); for (int i = 0 ; i < M ; i++) { int u,v; scanf("%d%d",&u,&v); add_edge(u,v,9797); } calcu(N,kase++); } return 0; }
HDU 4685 Prince and Princess
这题太吊了。反着我是完全不会。题解代码均是看了别人的。悟把!
复制的:
对于这个题,首先我们考虑一个简化版,即如同样例给出来的有 n 个王
子和公主,并且他们能够完全匹配的情况。
我们先求出最大匹配,这时我们如果对于每个王子和他喜欢的每个公
主都去枚举他们 link 的时候是否还能找到最大匹配,这样复杂度可能高达
O ( n 4),应该是会超时的。
那么我们换一个角度,枚举然后找最大匹配实质上是找到一个人和他互
换,并且他们喜欢他们互相配对的公主。那么我们可以试着直接用连边来
表示这个过程,对于每个王子,从他目前配对的公主出发连有向边到他喜
欢的所有公主,表示他配对的公主单方面可以和这些公主换。若是两者可
以互换,那么在图中就表示为了可以互相到达,从而想到对于这样的一个
图求出他的强连通分量,对于每个王子,枚举他喜欢的每个公主,如果这
个公主和他目前配对的公主在同一个强连通分量里,表示这个王子可以选
该公主。
然后回到这个题,这个题变成了 n 个王子和 m 个公主,其实最主要的
还是他们的最大匹配不一定是完全匹配。如果直接套用以上的方法就有问
题。比如有一个王子,两个公主,这个王子两个公主都喜欢,那么求出的
最大匹配只会和其中一个公主相连,通过上面的方法建图,两个公主并不
会在一个强连通分量里面,但实际上这个王子选两个中的任意一个都不会
使最大匹配减少。我们要怎么解决这个问题,这里又要从最本质的地方来
看,就是一个互换。
这里的王子如果求出匹配连的是一号公主,那么二号公主没有和任何王
子相连,这个王子就无法和任何人互换从而得到二号公主。考虑到这个问
题,很自然的就能想到给二号公主虚拟一个王子和她配对就好了,然后这
个虚拟的王子喜欢所有的公主。
因为这个公主本身是没有匹配的,如果通过虚拟一个喜欢所有公主的王
子向所有其他公主连边,使得这个公主得到了一个匹配,那么肯定会有另
外一边的匹配数减少了,因为最开始就求到的是最大匹配,如果这个公主
匹配增加并且没有任何一边的匹配减少,肯定违背了最大匹配的性质。
所以得证这样一种虚拟王子连边的方法不会减少匹配数并且正确。
剩下公主的问题考虑完了,现在考虑剩下王子的问题。同理,我们给每
一个没有匹配上的剩下的王子,虚拟一个公主,这个公主被所有王子喜欢,
那么和最开始的做法一样求强连通然后考虑是否在一个强连通分量里即可。
证明和剩下公主类似。
代码
#include <map> #include <set> #include <list> #include <cmath> #include <ctime> #include <deque> #include <stack> #include <queue> #include <cctype> #include <cstdio> #include <string> #include <vector> #include <climits> #include <cstdlib> #include <cstring> #include <iostream> #include <algorithm> #define LL long long #define PI 3.1415926535897932626 using namespace std; int gcd(int a, int b) {return a % b == 0 ? b : gcd(b, a % b);} const int MAXN = 1010; int un,vn; int N,M; int g[MAXN][MAXN]; int linker[MAXN]; bool used[MAXN]; bool dfs(int u) { for (int v = 1 ; v <= vn ; v++) { if (g[u][v] && !used[v]) { used[v] = true; if (linker[v] == -1 || dfs(linker[v])) { linker[v] = u; return true; } } } return false; } int hungry() { int res = 0; memset(linker,-1,sizeof(linker)); for (int u = 1 ; u <= un ; u++) { memset(used,false,sizeof(used)); if (dfs(u)) res++; } return res; } int lx[MAXN]; const int MAXM = 500010; struct Edge { int u,v,w; int next; }edge[MAXM]; int head[MAXN],tot; int Low[MAXN],DFN[MAXN],Stack[MAXN],Belong[MAXN]; int Index,top; int scc; bool Instack[MAXN]; int num[MAXN]; void init() { memset(head,-1,sizeof(head)); tot = 0; } void add_edge(int u,int v,int w) { edge[tot].u = u; edge[tot].v = v; edge[tot].w = w; edge[tot].next = head[u]; head[u] = tot++; } void Tarjan(int u) { int v; Low[u] = DFN[u] = ++Index; Instack[u] = true; Stack[top++] = u; for (int i = head[u] ; i != -1 ; i = edge[i].next) { int v = edge[i].v; if (!DFN[v]) { Tarjan(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 = Stack[--top]; Instack[v] = false; Belong[v] = scc; num[scc]++; }while (v != u); } } void calcu(int N) { memset(DFN,0,sizeof(DFN)); memset(Instack,false,sizeof(Instack)); memset(num,0,sizeof(num)); Index = top = 0; for (int i = 1; i <= N ; i++) { if (!DFN[i]) Tarjan(i); } } int main() { int kase = 1,T; scanf("%d",&T); while (T--) { scanf("%d%d",&N,&M); memset(g,0,sizeof(g)); for (int i = 1 ; i <= N ; i++) { int cnt; scanf("%d",&cnt); while (cnt--) { int x; scanf("%d",&x); g[i][x] = 1; } } un = N; vn = M; int res = hungry(); un = vn = N + M - res; for (int i = N + 1 ; i <= un ; i++) for (int j = 1 ; j <= vn ; j++) g[i][j] = 1; for (int i = 1 ; i <= un ; i++) for (int j = M + 1 ; j <= vn ; j++) g[i][j] = 1; hungry(); memset(lx,-1,sizeof(lx)); for (int i = 1 ; i <= vn ; i++) { if (linker[i] != -1) lx[linker[i]] = i; } init(); for (int i = 1 ; i <= un ; i++) for (int j = 1 ; j <= vn ; j++) { if (g[i][j] && j != lx[i]) { add_edge(lx[i],j,9797); } } calcu(vn); printf("Case #%d:\n",kase++); vector<int>ans; for (int i = 1 ; i <= N ; i++) { ans.clear(); for (int j = 1 ; j <= M ; j++) { if (g[i][j] && Belong[j] == Belong[lx[i]]) ans.push_back(j); } int sz = ans.size(); printf("%d",sz); for (int i = 0 ; i < sz ; i++) printf(" %d",ans[i]); putchar('\n'); } } return 0; }
HDU 4738 Caocao's Bridges
这个题就是很坑,不废话看了代码就知道坑在那
#pragma comment(linker, "/STACK:1024000000,1024000000") #include <map> #include <set> #include <list> #include <cmath> #include <ctime> #include <deque> #include <stack> #include <queue> #include <cctype> #include <cstdio> #include <string> #include <vector> #include <climits> #include <cstdlib> #include <cstring> #include <iostream> #include <algorithm> #define LL long long #define PI 3.1415926535897932626 using namespace std; int gcd(int a, int b) {return a % b == 0 ? b : gcd(b, a % b);} const int MAXN = 1010; const int MAXM = 2000010; const int INF = 0x3f3f3f3f; int fa[MAXN]; int Find(int x){return x == fa[x] ? x : fa[x] = Find(fa[x]);} struct Edge { int u,v,next; int w; int id; bool cut; }edge[MAXM]; int head[MAXN],tot; void init() { tot = 0; memset(head,-1,sizeof(head)); } void add_edge(int u,int v,int w,int id) { edge[tot].u = u; edge[tot].v = v; edge[tot].w = w; edge[tot].id = id; edge[tot].cut = false; edge[tot].next = head[u]; head[u] = tot++; } int Low[MAXN],DFN[MAXN],Stack[MAXN]; int Index,top; bool Instack[MAXN]; int bridge,N,M; void Tarjan(int u,int pre) { int v; Low[u] = DFN[u] = ++Index; Stack[top++] = u; Instack[u] = true; int son = 0; for (int i = head[u] ; i != -1 ; i = edge[i].next) { v = edge[i].v; if (edge[i].id == pre) continue; if (!DFN[v]) { son++; Tarjan(v,edge[i].id); if (Low[u] > Low[v]) Low[u] = Low[v]; if (Low[v] > DFN[u]) { bridge++; edge[i].cut = true; edge[i ^ 1].cut = true; } } else if (DFN[v] < DFN[u] && edge[i].id != pre) Low[u] = min(Low[u],DFN[v]); } /* if (Low[u] == DFN[u]) { block++; do { v = Stack[--top]; Instack[v] = true; Belong[v] = block; }while (v != u); } */ Instack[u] = false; top--; } void calcu(int N) { memset(Instack,false,sizeof(Instack)); memset(DFN,0,sizeof(DFN)); Index = top = bridge = 0; for (int i = 1; i <= N ; i++) if (!DFN[i]) Tarjan(i,-1); int ret = INF; for (int i = 0 ; i < tot ; i++) { if (edge[i].cut) ret = min(ret,edge[i].w); } if (ret == INF) printf("%d\n",-1); else printf("%d\n",max(1,ret)); } int main() { while (scanf("%d%d",&N,&M) == 2) { if (N == 0 && M == 0) break; for (int i = 0 ; i <= N ; i++) fa[i] = i; init(); for (int i = 1 ; i <= M ; i++) { int u,v,w; scanf("%d%d%d",&u,&v,&w); add_edge(u,v,w,i); add_edge(v,u,w,i); int fu = Find(u); int fv = Find(v); if (fu != fv) { fa[fu] = fv; } } int id = Find(1); bool flag = false; for (int i = 2 ; i <= N ; i++) { int fi = Find(i); if (fi != id) { flag = true; break; } } if (flag) puts("0"); else calcu(N); } return 0; }
以上是全部Kuangbin带你飞联通图题目
下面其他的
Gym 100338 C Important Roads
题意就是一个图。让你求出所有最短路中一定要走的路;就是最短路的干路
起初没想到是tarjan 做法就是最短路处理后重建图。跑桥就可以注意重边
同时处理出最短路树也是参照了别人的做法这么搞很有技巧!
代码
#include <map> #include <set> #include <list> #include <cmath> #include <ctime> #include <deque> #include <stack> #include <queue> #include <cctype> #include <cstdio> #include <string> #include <vector> #include <climits> #include <cstdlib> #include <cstring> #include <iostream> #include <algorithm> #define LL long long #define PI 3.1415926535897932626 using namespace std; int gcd(int a, int b) {return a % b == 0 ? b : gcd(b, a % b);} const int MAXN = 200010; const int MAXM = 200010; const LL INF = (1LL << 58); struct Edge { int u,v,next; LL w; int id; bool cut; }; struct node { LL val; int u; bool operator < (const node & rhs) const { return val > rhs.val; } }; struct Dijkstra { int N,M; LL dis[MAXN]; bool done[MAXN]; int head[MAXN],tot; Edge edge[MAXM]; void init(int n) { memset(head,-1,sizeof(head)); tot = 0; N = n; } void add_edge(int u,int v,LL w,int id) { edge[tot].u = u; edge[tot].v = v; edge[tot].w = w; edge[tot].id = id; edge[tot].next = head[u]; head[u] = tot++; } void dijkstra(int s) { memset(done,false,sizeof(done)); for (int i = 0 ; i <= N ; i++) dis[i] = INF; dis[s] = 0; priority_queue<node>q; while (!q.empty()) q.pop(); q.push((node){dis[s],s}); while (!q.empty()) { node x = q.top(); q.pop(); int u = x.u; // cout << u << " " << dis[u] << endl ; if (done[u]) continue; done[u] = true; for (int i = head[u] ; i != -1 ; i = edge[i].next) { int v = edge[i].v; if (dis[v] > dis[u] + edge[i].w) { dis[v] = dis[u] + edge[i].w; q.push((node){dis[v],v}); } } } } }S,T; Edge edge[MAXM]; int head[MAXN],tot; int Low[MAXN],DFN[MAXN],Stack[MAXN]; int Index,top; bool Instack[MAXN]; bool cut[MAXN]; int add_block[MAXN]; int bridge; void init() { memset(head,-1,sizeof(head)); tot = 0; } void add_edge(int u,int v,LL w,int id) { edge[tot].u = u; edge[tot].v = v; edge[tot].w = w; edge[tot].id = id; edge[tot].next = head[u]; edge[tot].cut = false; head[u] = tot++; } void Tarjan(int u,int pre) { int v; Low[u] = DFN[u] = ++Index; Stack[top++] = u; Instack[u] = true; int son = 0; for (int i = head[u] ; i != -1 ; i = edge[i].next) { v = edge[i].v; if (!DFN[v]) { son++; Tarjan(v,edge[i].id); if (Low[u] > Low[v]) Low[u] = Low[v]; if (Low[v] > DFN[u]) { bridge++; edge[i].cut = true; edge[i ^ 1].cut = true; } if (pre != -1 && Low[v] >= DFN[u]) { cut[u] = true; add_block[u]++; } } else if (DFN[v] < DFN[u] && edge[i].id != pre) Low[u] = min(Low[u],DFN[v]); } if (pre == -1 && son > 1) cut[u] = true; if (pre == -1) add_block[u] = son - 1; Instack[u] = false; top--; } set<int>ans; set<int>::iterator it; void slove(int N) { memset(DFN,0,sizeof(DFN)); memset(Instack,false,sizeof(Instack)); memset(add_block,0,sizeof(add_block)); memset(cut,false,sizeof(cut)); Index = top = bridge = 0; Tarjan(1,-1); // printf("%d\n",bridge); ans.clear(); for (int i = 0 ; i < tot ; i++) { if (edge[i].cut) ans.insert(edge[i].id); } printf("%d\n",ans.size()); for (it = ans.begin() ; it != ans.end() ; it++) printf("%d ",*it); } int u[MAXN],v[MAXN]; LL w[MAXN]; int main() { freopen("important.in","r",stdin); freopen("important.out","w",stdout); int n,m,s,t; while (scanf("%d%d",&n,&m) != EOF) { s = 1; t = n; S.init(n); T.init(n); for (int i = 1 ; i <= m ; i++) { scanf("%d%d%I64d",&u[i],&v[i],&w[i]); S.add_edge(u[i],v[i],w[i],i); S.add_edge(v[i],u[i],w[i],i); T.add_edge(u[i],v[i],w[i],i); T.add_edge(v[i],u[i],w[i],i); } S.dijkstra(s); T.dijkstra(t); init(); for (int i = 1 ; i <= m ; i++) { int tu = u[i]; int tv = v[i]; LL tw = w[i]; if ((S.dis[tu] + tw == S.dis[tv] && T.dis[tv] + tw == T.dis[tu]) ||(S.dis[tv] + tw == S.dis[tu] && T.dis[tu] + tw == T.dis[tv])) { add_edge(tu,tv,tw,i); add_edge(tv,tu,tw,i); } } slove(n); } return 0; }