pku 2762 Going from u to v or from v to u?
题意:给定一个有向图,对任意一对x,y,判断是否存在x到y 或 y到x 的路径。。。
分析:首先,可以想到的是,强连通缩点,因为对于一个极大强连通分量,任意俩点均可达,所以先缩成一个点,,缩点之后,得到的是一个DAG,判断图是否为弱连通图==》即判断拓扑序列是否唯一
#include<iostream> #include<algorithm> #include<vector> #include<stack> #include<queue> #include<stdio.h> #include<string.h> #include<stdlib.h> using namespace std; const int N = 1000+10; vector<int> g[N],g1[N]; stack<int> st; int dfn[N],low[N],f[N],n,num,index; bool instack[N],vis[N]; int deg[N]; void tarjan(int u) { int v; vis[u]=true; dfn[u]=low[u]=index++; st.push(u); instack[u]=true; for(int i=0;i<g[u].size();++i) { v=g[u][i]; if(!vis[v]) { tarjan(v); low[u]=min(low[u],low[v]); } else if(instack[v]) low[u]=min(low[u],dfn[v]); } if(low[u]==dfn[u]) { do { v=st.top(); st.pop(); instack[v]=false; f[v]=num; }while(v!=u); num++; } } bool topo() { queue<int> Q; for(int i=0;i<num;++i) if(deg[i]==0) Q.push(i); if(Q.size()>=2) return false; while(!Q.empty()) { int u=Q.front(); Q.pop(); for(int i=0;i<g1[u].size();i++) { --deg[g1[u][i]]; if(deg[g1[u][i]]==0) Q.push(g1[u][i]); } if(Q.size()>=2) return false; } return true; } int main() { int T,m; int a,b; scanf("%d",&T); while(T--) { scanf("%d %d",&n,&m); for(int i=0;i<=n;++i) { g[i].clear(); g1[i].clear(); } while(m--) { scanf("%d %d",&a,&b); g[a].push_back(b); } memset(instack,false,sizeof(instack)); memset(vis,false,sizeof(vis)); index=num=0; for(int i=1;i<=n;i++) if(!vis[i]) tarjan(i); memset(deg,0,sizeof(deg)); for(int i=1;i<=n;i++) for(int j=0;j<g[i].size();j++) { if(f[i]!=f[g[i][j]]) { g1[f[i]].push_back(f[g[i][j]]); ++deg[f[g[i][j]]]; } } if(topo()) puts("Yes"); else puts("No"); } return 0; }
pku 3592 Instantaneous Transference
题意:在一个给定的N *M 的矩阵上,数字代表该位置的矿物数,,* 代表该位置可以选择是否移动到某一个特定的位置,#不可通过,从左上角出发,每次只能向右或向下,或者某些位置的瞬间移动,问可能得到的矿物数的最大值(每个位置的矿物只能取一次)
分析:若不存在瞬间移动的可能,则可以直接建图,得到的是一个DAG ,然后直接一遍dfs 即可,,问题是有了瞬间移动的可能,所以需要对该图求强连通分量,缩点,同样得到一个DAG ,,
#include<iostream> #include<algorithm> #include<vector> #include<stack> #include<stdio.h> #include<string.h> #include<stdlib.h> using namespace std; const int N = 1600+10; int dfn[N],low[N],f[N],num,index; bool instack[N],vis[N]; vector<int> g[N],g1[N]; stack<int> st; int x[N],y[N],w[N],ww[N]; char map[50][50]; void init(int n) { for(int i=0;i<n;i++) { g[i].clear(); g1[i].clear(); } memset(vis,false,sizeof(vis)); memset(instack,false,sizeof(instack)); memset(ww,0,sizeof(ww)); memset(w,0,sizeof(w)); num=index=0; } void tarjan(int u) { dfn[u]=low[u]=index++; vis[u]=true; st.push(u); instack[u]=true; int v; for(int i=0;i<g[u].size();++i) { v=g[u][i]; if(!vis[v]) { tarjan(v); low[u]=min(low[u],low[v]); } else if(instack[v]) low[u]=min(low[u],dfn[v]); } if(low[u]==dfn[u]) { do { v=st.top(); st.pop(); instack[v]=false; f[v]=num; //cout<<num<<' '<<v<<' '<<w[v]<<endl; ww[num]+=w[v]; }while(v!=u); num++; } } int dfs(int u) { int temp=0; for(int i=0;i<g1[u].size();++i) temp=max(temp,dfs(g1[u][i])); //cout<<temp+ww[u]<<endl; return temp+ww[u]; } int main() { int n,m,a,b; int T; scanf("%d",&T); while(T--) { scanf("%d %d",&n,&m); init(n*m); int k=0; for(int i=0;i<n;i++) scanf("%s",map[i]); for(int i=0;i<n;++i) for(int j=0;j<m;++j) { if(map[i][j]=='*') x[k]=i,y[k++]=j; if(map[i][j]=='#') continue; if(map[i][j]!='*') w[i*m+j]=map[i][j]-'0'; if(j+1<m && map[i][j+1]!='#') g[i*m+j].push_back(i*m+j+1); if(i+1<n && map[i+1][j]!='#') g[i*m+j].push_back((i+1)*m+j); } for(int i=0;i<k;++i) { scanf("%d %d",&a,&b); g[x[i]*m+y[i]].push_back(a*m+b); } for(int i=0;i<n*m;++i) if(!vis[i]) tarjan(i); for(int i=0;i<n*m;++i) for(int j=0;j<g[i].size();++j) { if(f[i]!=f[g[i][j]]) g1[f[i]].push_back(f[g[i][j]]); } //for(int i=0;i<num;i++) // cout<<ww[i]<<endl; int ans=dfs(f[0]); printf("%d\n",ans); } return 0; }
题意:给定一个无向图,,无重边,问删除一个点之后,可以得到的最大的连通分量数
分析:首先,都知道,删除割点,可以得到多个连通分量,可以得到的连通分量数==与该割点连接的点双连通分支数
若该图本身是连通的,则ans==最大的与某一个割点连接的点双连通分支数;
若本身不连通,还需要加上一开始的连通分支数-1
#include<iostream> #include<algorithm> #include<string.h> #include<stdio.h> #include<stdlib.h> #include<vector> #include<stack> #include<set> using namespace std; const int N = 10000+10; int dfn[N],low[N],n,index; int cut[N]; bool vis[N]; vector<int> g[N]; void init() { for(int i=0;i<n;i++) g[i].clear(); memset(vis,false,sizeof(vis)); memset(cut,0,sizeof(cut)); index=0; } void tarjan(int u,int p) { int v; dfn[u]=low[u]=index++; vis[u]=true; int sun=0; for(int i=0;i<g[u].size();++i) { v=g[u][i]; if(!vis[v]) { tarjan(v,u); ++sun; low[u]=min(low[u],low[v]); if((p==-1 && sun>1)||(p!=-1 && dfn[u]<=low[v])) { ++cut[u]; } } else if(v!=p) low[u]=min(low[u],dfn[v]); } } int main() { int m; int a,b; while(scanf("%d %d",&n,&m)==2 && (n||m)) { if(n==1) { printf("0\n"); continue; } if(m==0) { printf("%d\n",n-1); continue; } init(); while(m--) { scanf("%d %d",&a,&b); g[a].push_back(b); g[b].push_back(a); } int t=0; for(int i=0;i<n;i++) if(!vis[i]) { ++t; tarjan(i,-1); } int ans=0; for(int i=0;i<n;i++) { if(cut[i]) ans=max(ans,cut[i]); } printf("%d\n",ans+t); } return 0; }
pku2942 Knights of the Round Table
题意:N个骑士开圆桌会议, 有些骑士不能相邻, 要求每桌奇数个人, 每桌至少3个人, 问哪些骑士
永远不能参加圆桌会议.
永远不能参加会议的人数!!而不是最少不能参加的人数!!所以,图可以不连通
分析:首先对于每一个点双连通分量,判断该连通分量是否存在连接所有点的奇圈,若存在,则很明显,该点双连通分量中所有点都是可能参加圆桌会议的,,又因为,对于一个点双连通分量,若存在一个奇圈,则该连通分量在所有点都在一个更大的奇圈中,所以,对于每一个点连通分量,只需要判断是否存在奇圈即可。。
注意:割点同时属于多个点双连通分量
//求双连通分量 , 判奇圈(用染色法) #include<iostream> #include<algorithm> #include<vector> #include<stack> #include<string.h> //题意:N个骑士开圆桌会议, 有些骑士不能相邻, 要求每桌奇数个人, 每桌至少3个人, 问哪些骑士 //永远不能参加圆桌会议. //永远不能参加会议的人数!!而不是最少不能参加的人数!!所以,图可以不连通 #include<stdio.h> #include<stdlib.h> using namespace std; const int N = 1000+10; int n,low[N],dfn[N],index,set[N],col[N]; bool map[N][N],vis[N],scc[N],flag[N]; //scc[] 标记一个点是否在当前的双连通分量中 //set[] 当前双连通分量的点集 //flag[] 标记一个点是否在奇圈中 vector<int> g[N]; stack<int> st; bool dfs(int u,int c)//染色法,判断是否存在奇圈 { col[u]=c; for(int i=0;i<g[u].size();++i) { int v=g[u][i]; if(!scc[v]) continue; if(col[v]==-1) { if(dfs(v,1-c)) return true; } else if(col[v]==col[u]) return true; } return false; } void check(int k) { int i; memset(col,-1,sizeof(col)); if(dfs(set[0],0)) { for(int i=0;i<k;i++) flag[set[i]]=true; } memset(scc,false,sizeof(scc)); } void tarjan(int u,int p) { dfn[u]=low[u]=index++; vis[u]=true; st.push(u); for(int i=0;i<g[u].size();++i) { int v=g[u][i]; if(v==p) continue; if(!vis[v]) { tarjan(v,u); low[u]=min(low[u],low[v]); if(dfn[u]<=low[v]) { int tmp,k=1; set[0]=u; do { tmp=st.top(); st.pop(); scc[tmp]=true; set[k++]=tmp; }while(tmp!=v); check(k); } } else low[u]=min(low[u],dfn[v]); } } void init() { for(int i=0;i<n;++i) g[i].clear(); memset(vis,false,sizeof(vis)); memset(scc,false,sizeof(scc)); memset(flag,false,sizeof(flag)); index=0; for(int i=0;i<n;++i) for(int j=i+1;j<n;++j) if(!map[i][j]) { g[i].push_back(j); g[j].push_back(i); } } int main() { int a,b,m; while(scanf("%d %d",&n,&m)==2 && (n||m)) { memset(map,false,sizeof(map)); while(m--) { scanf("%d %d",&a,&b); --a;--b; map[a][b]=map[b][a]=true; } init(); for(int i=0;i<n;++i) if(!vis[i]) tarjan(i,-1); int ans=0; for(int i=0;i<n;++i) if(flag[i]) ++ans; printf("%d\n",n-ans); } return 0; }
pku3160 Father Christmas flymouse
题意 :在一个有向图中,选择一个点出发,沿着有向边走,每一个点都有一个权值(可能为负),但只能获得该点的权值一次,可以选择不要(负数的时候),问最终可能获得的最大权值和
分析:此题与上面的pku3592类似,建图完成之后就一样了,只是这里权值可能为负,要特殊考虑一下
#include<iostream> #include<algorithm> #include<stack> #include<vector> using namespace std; const int N = 30000+10; int dfn[N],low[N],f[N],n,num,index; bool vis[N],instack[N]; int w[N],ww[N],deg[N]; vector<int> g[N],g1[N]; stack<int> st; void tarjan(int u) { dfn[u]=low[u]=index++; vis[u]=true; st.push(u); instack[u]=true; int v; for(int i=0;i<g[u].size();++i) { v=g[u][i]; if(!vis[v]) { tarjan(v); low[u]=min(low[u],low[v]); } else if(instack[v]) low[u]=min(low[u],dfn[v]); } if(dfn[u]==low[u]) { do { v=st.top(); st.pop(); instack[v]=false; f[v]=num; ww[num]+=w[v]>0?w[v]:0; }while(v!=u); num++; } } void init() { for(int i=0;i<n;++i) g[i].clear(); memset(vis,false,sizeof(vis)); memset(instack,false,sizeof(instack)); memset(ww,0,sizeof(ww)); memset(deg,0,sizeof(deg)); num=index=0; } int dfs(int u) { int ret=ww[u],tmp=0; if(ret<0) ret=0; for(int i=0;i<g1[u].size();++i) tmp=max(tmp,dfs(g1[u][i])); return ret+=tmp; } int main() { int m; int a,b; while(scanf("%d %d",&n,&m)==2) { init(); for(int i=0;i<n;++i) scanf("%d",&w[i]); while(m--) { scanf("%d %d",&a,&b); g[a].push_back(b); } for(int i=0;i<n;++i) if(!vis[i]) tarjan(i); for(int i=0;i<num;++i) g1[i].clear(); for(int i=0;i<n;++i) for(int j=0;j<g[i].size();++j) if(f[i]!=f[g[i][j]]) { g1[f[i]].push_back(f[g[i][j]]); deg[f[g[i][j]]]++; } int ans=0; for(int i=0;i<num;++i) { if(deg[i]==0)//当然是选择入度为零的点开始搜索 ans=max(ans,dfs(i)); } printf("%d\n",ans); } return 0; }
题意:给定一个2*N个顶点的2分图,并且给了一个完美匹配(Perfect Matching)以及每个顶点可以连接的其他的顶点。
//题目要求是否可以确定某2个顶点连边后,其他2*(N - 1)个顶点的2分图是否可以构成完美匹配。
//分析:
//题目给了你一个初始的最大匹配 怎样用好这个匹配是非常关键的
//首先把图转化成有向图(因为后面我们要用到连通性判定)
//现在令王子为A集合,公主为B集合
//原图中所有的边转化成A->B的边 然后对原来的每一个匹配Ai->Bj 添加一条从Bj到Ai的边
//结论: 如果Ai,Bj能够互访 我们则认为Ai, Bj作为一条匹配边仍然不会影响其他王子和自己心爱的人匹配
//证明如下:
//现在有一个匹配Ai->Bj 我们知道Ai和Bj可以互访
//我们要验证其是否可以成立时对其他王子找MM没有影响
//1。如果题目中给的初始匹配包含这条边 则题目给出的初始匹配就证明了这位王子的公德心
//2。如果题目没有给出这条边 给出的是Ai -> Bk 由于Ai与Bk也可互访 所以存在Bj->Ai->Bk的增广路 也就是说可以建立另外一个匹配A?->Bk
//所以同一个连通分量内部是可以换匹配的
以上来自网络上的分析,已经很清晰
#include<iostream> #include<algorithm> #include<vector> #include<stack> #include<stdio.h> #include<string.h> #include<stdlib.h> using namespace std; const int N = 4000+10; int dfn[N],low[N],f[N],index,n,num; bool vis[N],instack[N]; vector<int> g[N]; stack<int> st; int set[N]; void tarjan(int u) { dfn[u]=low[u]=index++; vis[u]=true; st.push(u); instack[u]=true; int v; for(int i=0;i<g[u].size();++i) { v=g[u][i]; if(!vis[v]) { tarjan(v); low[u]=min(low[u],low[v]); } else if(instack[v]) low[u]=min(low[u],dfn[v]); } if(dfn[u]==low[u]) { do { v=st.top(); st.pop(); instack[v]=false; f[v]=num; }while(v!=u); num++; } } void init() { for(int i=0;i<(n<<1);++i) g[i].clear(); memset(vis,false,sizeof(vis)); memset(instack,false,sizeof(instack)); num=index=0; } int main() { int k,a; while(scanf("%d",&n)==1) { init(); for(int i=0;i<n;++i) { scanf("%d",&k); while(k--) { scanf("%d",&a); g[i].push_back(a+n-1); } } for(int i=0;i<n;++i) { scanf("%d",&a); g[a+n-1].push_back(i); } for(int i=0;i<n;++i) if(!vis[i]) tarjan(i); for(int i=0;i<n;++i) { int k=0; for(int j=0;j<g[i].size();++j) { int u=i,v=g[i][j]; if(v<n) continue; if(f[u]==f[v]) set[k++]=v-n+1; } sort(set,set+k); printf("%d",k); for(int j=0;j<k;++j) printf(" %d",set[j]); puts(""); } } return 0; }
pku3352 Road Construction && pku3177 Redundant Paths
题意:给定一个无向图,问需要添加的最少边数,将该图边为边双连通图
分析:
//在原图中DFS求出所有的桥,然后删除这些桥边,剩下的每个连通块都是一个双连通子图。
//把每个双连通子图收缩为一个顶点,再把桥边加回来,最后的这个图一定是一棵树,
//边连通度为1。统计出树中度为1的节点的个数,即为叶节点的个数,记为leaf。有人
//说至少在树上添加(leaf+1)/2条边,就能使树达到边二连通,所以结果就是(leaf+1)/2。
//题目大意是,给定一个连通图,要求添加一些边,使每两个顶点之间都有至少两条不 //相交的路径,求最小需要添加的边数。 // //很显然,题中要求的图就是一个边双连通图,即边连通度不小于2。我的方法是在原 //图中DFS求出所有的桥,然后删除这些桥边,剩下的每个连通块都是一个双连通子图。 //把每个双连通子图收缩为一个顶点,再把桥边加回来,最后的这个图一定是一棵树, //边连通度为1。统计出树中度为1的节点的个数,即为叶节点的个数,记为leaf。有人 //说至少在树上添加(leaf+1)/2条边,就能使树达到边二连通,所以结果就是(leaf+1)/2。 #include<iostream> #include<algorithm> #include<vector> #include<stack> using namespace std; const int N= 1000+10; int dfn[N],low[N],f[N],index,n,num; bool vis[N]; int deg[N]; vector<int> g[N]; stack<int> st; int find(int x) { if(x==f[x]) return f[x]; f[x]=find(f[x]); return f[x]; } void Union(int x,int y) { int a=find(x),b=find(y); if(a==b) return ; f[a]=b; } void tarjan(int u,int p) { dfn[u]=low[u]=index++; vis[u]=true; st.push(u); int v; bool flag=true; for(int i=0;i<g[u].size();++i) { v=g[u][i]; if(v==p && flag) { flag=false; continue; } if(!vis[v]) { tarjan(v,u); low[u]=min(low[u],low[v]); if(!(dfn[u]<low[v]))//不是桥,则(u,v) 属于同一个边双连通分量 { Union(u,v); } } else low[u]=min(low[u],dfn[v]); } } void init() { for(int i=0;i<n;i++) g[i].clear(); for(int i=0;i<n;i++) f[i]=i; memset(vis,false,sizeof(vis)); memset(deg,0,sizeof(deg)); num=index=0; } int main() { int m; int a,b; while(scanf("%d %d",&n,&m)==2) { init(); while(m--) { scanf("%d %d",&a,&b); --a;--b; g[a].push_back(b); g[b].push_back(a); } for(int i=0;i<n;++i) if(!vis[i]) tarjan(i,-1); for(int i=0;i<n;++i) { f[i]=find(i); for(int j=0;j<g[i].size();++j) { f[g[i][j]]=find(g[i][j]); if(f[i]!=f[g[i][j]]) { deg[f[i]]++; deg[f[g[i][j]]]++; } } } int ans=0; for(int i=0;i<n;++i) if(deg[i]==2) ans++; printf("%d\n",(ans+1)>>1); } return 0; }
hdu 3894 By Recognizing These Guys, We Find Social Networks Useful
题意:给定一个无向图,求桥,要求按照给定的顺序输出
分析:给每一条边同时加上编号,若为桥,则记录标号即可,对名字的处理(用字典树给每一个名字标号)
PS:不连通或不存在桥需要特判一下
#include<iostream> #include<algorithm> #include<vector> #include<stack> #include<string.h> #include<stdlib.h> #include<stdio.h> using namespace std; const int N = 10000+10; const int kind = 26; struct edge { int u,v; edge(int u=0,int v=0):u(u),v(v){} }e[N*10]; struct edge1 { int v,id; edge1(int u=0,int d=0):v(u),id(d){} }; int dfn[N],low[N],n,index,num,k,ans[N*10]; char str[N][20]; bool vis[N]; vector<edge1> g[N]; struct node { int flag; node *next[kind]; node() { flag=-1; memset(next,NULL,sizeof(next)); } }; int insert(char *s,node *root) { node *p=root; int i=0; while(s[i]) { int in=s[i]-'a'; if(p->next[in]==NULL) p->next[in]=new node(); p=p->next[in]; i++; } if(p->flag==-1) p->flag=num++; return p->flag; } void tarjan(int u,int p) { dfn[u]=low[u]=index++; vis[u]=true; int v; for(int i=0;i<g[u].size();++i) { v=g[u][i].v; if(v==p) continue; if(!vis[v]) { tarjan(v,u); low[u]=min(low[u],low[v]); if(dfn[u]<low[v]) ans[k++]=g[u][i].id; } else low[u]=min(low[u],dfn[v]); } } void init() { for(int i=0;i<n;++i) g[i].clear(); num=index=k=0; memset(vis,false,sizeof(vis)); } int main() { char s1[20],s2[20]; int m,a,b; int T; scanf("%d",&T); while(T--) { node *root=new node(); init(); scanf("%d %d",&n,&m); for(int i=0;i<m;++i) { scanf("%s %s",s1,s2); a=insert(s1,root); b=insert(s2,root); e[i]=edge(a,b); strcpy(str[a],s1); strcpy(str[b],s2); g[a].push_back(edge1(b,i)); g[b].push_back(edge1(a,i)); } int t=0; for(int i=0;i<n;i++) if(!vis[i]) { t++; if(t>1) break; tarjan(i,-1); } if(t!=1) { printf("0\n"); continue; } printf("%d\n",k); if(k==0) continue; sort(ans,ans+k); for(int i=0;i<k;++i) printf("%s %s\n",str[e[ans[i]].u],str[e[ans[i]].v]); } return 0; }
题意:给定一个有N个点,M条边的无向图,求有多少条边没有在环内,有多少条边在至少2个环内。
思路:点双连通,对于每个点双连通,分别求出顶点数,记为a , 和边数,记为b,当a < b ,即顶点数小于边数的时候,则可以证明,这时候连通区域内的边都是clash边,当a == b的时候,则可以得出此时刚好是一个环,每条边都只在正好两个环内,当a > b的时候,这时候连通分量是一棵树,(其实只有两个顶点的时候才可能出现这种情况),此时的边都是没有构成环的边。
#include<iostream> #include<algorithm> #include<vector> #include<stack> using namespace std; const int N = 10000+10; vector<int> g[N]; stack<int> st; int dfn[N],low[N],n,num,index; bool vis[N],ins[N]; int sccn[N],ans1,ans2; void get_ans(int cnt) { int e=0; for(int i=0;i<cnt;++i) { int u=sccn[i]; for(int j=0;j<g[u].size();++j) { if(ins[g[u][j]]) ++e; } } e>>=1; if(e>cnt) ans2+=e; if(e<cnt) ans1+=e; for(int i=0;i<cnt;++i) ins[sccn[i]]=false; } void tarjan(int u,int p) { dfn[u]=low[u]=index++; vis[u]=true; st.push(u); int v; for(int i=0;i<g[u].size();++i) { v=g[u][i]; if(v==p) continue; if(!vis[v]) { tarjan(v,u); low[u]=min(low[u],low[v]); if(dfn[u]<=low[v]) { int tmp,cnt=0; do { tmp=st.top(); st.pop(); sccn[cnt++]=tmp; ins[tmp]=true; }while(tmp!=v); ins[u]=true; sccn[cnt++]=u; get_ans(cnt); } } else low[u]=min(low[u],dfn[v]); } } void init() { for(int i=0;i<n;++i) g[i].clear(); memset(sccn,0,sizeof(sccn)); memset(vis,false,sizeof(vis)); memset(ins,false,sizeof(ins)); num=index=ans1=ans2=0; } int main() { int m,a,b; while(scanf("%d %d",&n,&m)==2 && (n||m)) { init(); while(m--) { scanf("%d %d",&a,&b); g[a].push_back(b); g[b].push_back(a); } for(int i=0;i<n;++i) if(!vis[i]) tarjan(i,-1); printf("%d %d\n",ans1,ans2); } return 0; }
题意:简单的说,就是求割点,同时计算该割点所连接的点双连通分量的数目
#include<iostream> #include<algorithm> #include<vector> #include<stack> using namespace std; const int N = 1000+10; vector<int> g[N]; stack<int> st; int dfn[N],low[N],n,index; bool vis[N]; int cut[N]; void init() { for(int i=1;i<=1000;++i) g[i].clear(); memset(vis,false,sizeof(vis)); memset(cut,0,sizeof(cut)); index=0; } void tarjan(int u,int p) { int v; dfn[u]=low[u]=index++; vis[u]=true; int sun=0; for(int i=0;i<g[u].size();++i) { v=g[u][i]; if(!vis[v]) { tarjan(v,u); ++sun; low[u]=min(low[u],low[v]); if((p==-1 && sun>1)||(p!=-1 && dfn[u]<=low[v])) { ++cut[u]; } } else if(v!=p) low[u]=min(low[u],dfn[v]); } } int main() { int cas=0; int a,b; while(scanf("%d",&a) && a) { init(); n=0; n=max(n,a); scanf("%d",&b); n=max(n,b); g[a].push_back(b); g[b].push_back(a); while(scanf("%d",&a)&&a) { scanf("%d",&b); n=max(n,max(a,b)); g[a].push_back(b); g[b].push_back(a); } for(int i=1;i<=n;++i) if(!vis[i]) tarjan(i,-1); printf("Network #%d\n",++cas); int c=0; for(int i=1;i<=n;++i) { if(cut[i]) { ++c; printf(" SPF node %d leaves %d subnets\n",i,cut[i]+1); } } if(c==0) puts(" No SPF nodes"); puts(""); } return 0; }
题意:给定一个有向图,如果vertex归属的连通分量的出度为零,则输出该点;输出要从小到大排序
#include<iostream> #include<algorithm> #include<vector> #include<stack> using namespace std; const int N = 5000+10; int dfn[N],low[N],f[N],n,num,index; bool vis[N],instack[N]; vector<int> g[N]; stack<int> st; int ans[N],deg[N]; void tarjan(int u) { dfn[u]=low[u]=index++; vis[u]=true; st.push(u); instack[u]=true; int v; for(int i=0;i<g[u].size();++i) { v=g[u][i]; if(!vis[v]) { tarjan(v); low[u]=min(low[u],low[v]); } else if(instack[v]) low[u]=min(low[u],dfn[v]); } if(dfn[u]==low[u]) { do { v=st.top(); st.pop(); instack[v]=false; f[v]=num; }while(v!=u); num++; } } void init() { for(int i=0;i<=n;++i) g[i].clear(); memset(vis,false,sizeof(vis)); memset(instack,false,sizeof(instack)); num=index=0; } int main() { int m; int a,b; while(scanf("%d",&n)==1 && n) { scanf("%d",&m); init(); while(m--) { scanf("%d %d",&a,&b); g[a].push_back(b); } for(int i=1;i<=n;++i) if(!vis[i]) tarjan(i); memset(deg,0,sizeof(deg)); for(int i=1;i<=n;++i) for(int j=0;j<g[i].size();++j) if(f[i]!=f[g[i][j]]) deg[f[i]]++; int k=0; for(int i=0;i<num;i++) { if(deg[i]==0) { for(int j=1;j<=n;++j) if(f[j]==i) ans[k++]=j; } } if(k==0) { puts(""); continue; } sort(ans,ans+k); printf("%d",ans[0]); for(int i=1;i<k;++i) printf(" %d",ans[i]); puts(""); } return 0; }
题意:给定一个无向图,尽可能多的将双向边定向,变成单向边,同时保证该图是强连通图
分析:用tarjan 算法,桥必须是双向边,其余边按遍历的方向输出即可,仔细想想确实如此,证明嘛,,,,,,
#include<iostream> #include<algorithm> #include<vector> #include<stack> using namespace std; const int N = 1000+10; struct edge { int v,id; edge(int v=0,int id=0):v(v),id(id){} }; vector<edge> g[N]; int dfn[N],low[N],n,index; bool vis[N],ins[N*N]; void tarjan(int u,int p) { dfn[u]=low[u]=index++; vis[u]=true; int v; for(int i=0;i<g[u].size();++i) { v=g[u][i].v; if(v==p) continue; if(!vis[v]) { tarjan(v,u); low[u]=min(low[u],low[v]); if(dfn[u]<low[v]) { printf("%d %d\n",u,v); printf("%d %d\n",v,u); ins[g[u][i].id]=true; } } else low[u]=min(low[u],dfn[v]); if(!ins[g[u][i].id]) { ins[g[u][i].id]=true; printf("%d %d\n",u,v); } } } void init() { for(int i=0;i<=n;++i) g[i].clear(); memset(vis,false,sizeof(vis)); memset(ins,false,sizeof(ins)); index=0; } int main() { int m,a,b,cas=0; while(scanf("%d %d",&n,&m)==2 && (n||m)) { init(); for(int i=0;i<m;++i) { scanf("%d %d",&a,&b); g[a].push_back(edge(b,i)); g[b].push_back(edge(a,i)); } printf("%d\n\n",++cas); for(int i=1;i<=n;++i) if(!vis[i]) tarjan(i,-1); puts("#"); } return 0; }
题意:与上一题类似,只不过给的边有的是单向边,标记一下就好了(不可能存在单向边的桥,所以不用特判,是桥的话,一定得是双向边)
#include<iostream> #include<algorithm> #include<vector> #include<stack> #include<stdio.h> #include<string.h> #include<stdlib.h> using namespace std; const int N = 2000+10; struct edge { int v,id; bool flag; edge(int v=0,int id=0,bool f=false):v(v),id(id),flag(f){} }; vector<edge> g[N]; int dfn[N],low[N],n,index; bool vis[N],flag[N*N]; void tarjan(int u,int p) { dfn[u]=low[u]=index++; vis[u]=true; int v; for(int i=0;i<g[u].size();++i) { v=g[u][i].v; if(v==p) continue; if(!vis[v]) { tarjan(v,u); low[u]=min(low[u],low[v]); if(dfn[u]<low[v]) { printf("%d %d 2\n",u,v); flag[g[u][i].id]=true; } } else low[u]=min(low[u],dfn[v]); if(!flag[g[u][i].id] && g[u][i].flag) { printf("%d %d 1\n",u,v); flag[g[u][i].id]=true; } } } void init() { for(int i=0;i<=n;++i) g[i].clear(); memset(vis,false,sizeof(vis)); memset(flag,false,sizeof(flag)); index=0; } int main() { int a,b,c,m; while(scanf("%d %d",&n,&m)==2) { init(); for(int i=0;i<m;++i) { scanf("%d %d %d",&a,&b,&c); if(c==1) g[a].push_back(edge(b,i,false)); else { g[a].push_back(edge(b,i,true)); g[b].push_back(edge(a,i,true)); } } for(int i=1;i<=n;++i) if(!vis[i]) tarjan(i,-1); } return 0; }
题意:给定一个无向图,问没添加一条边之后,该图有多少条割边
分析:tarjan + 并查集 +暴力LCA
这题里面,并查集的作用至关重要,,首先利用tarjan求出边双连通分量以及割边的总数,之后,,利用tarjan算法时的时间戳暴力求出LCA。我们注意到,对于已经求得的双连通分量缩点之后,如果用桥连接起来,得到的是一棵树,那么每添加一条连通分量以外的边,就会形成一个环,此时,环内所有的桥都被销毁了,所以,我们可以利用求LCA过程中求出经过的哪些桥,同时标记(即将桥直接加到该连通分量中),,,
分别利用vector 和 邻接表运行的结果是好几倍的时间差距。。。
vector 是 3000+ms 而邻接表居然800+ms
#include<iostream> #include<algorithm> #include<string.h> #include<stdlib.h> #include<stdio.h> using namespace std; const int N = 100000+10; const int M = 500000+10; struct edge { int v,nex; }E[M]; int head[N],size; int dfn[N],low[N],f[N],index,n; int pre[N],sum; bool vis[N]; void init() { memset(head,-1,sizeof(head)); memset(vis,false,sizeof(vis)); index=sum=0; size=0; for(int i=0;i<=n;++i) f[i]=i; } void insert(int u,int v) { E[size].v=v; E[size].nex=head[u]; head[u]=size++; } int find(int x) { if(x==f[x]) return f[x]; f[x]=find(f[x]); return f[x]; } bool Union(int x,int y) { int a=find(x),b=find(y); if(a==b) return false; f[a]=b; return true; } void tarjan(int u,int p) { vis[u]=true; dfn[u]=low[u]=index++; int v; bool flag=true; for(int i=head[u];i!=-1;i=E[i].nex) { v=E[i].v; if(v==p && flag){ flag=false; continue; } if(!vis[v]) { tarjan(v,u); pre[v]=u; low[u]=min(low[u],low[v]); if(dfn[u]<low[v]) sum++; else Union(u,v); } else low[u]=min(low[u],dfn[v]); } } void LCA(int u,int v) { while(u!=v) { while(dfn[u]>dfn[v] && u!=v)//利用时间戳以及pre 数组,回溯到LCA { if(Union(u,pre[u])) sum--; else u=pre[u]; } while(dfn[v]>dfn[u] && u!=v) { if(Union(v,pre[v])) sum--; else v=pre[v]; } } } int main() { int cas=0,m,q,a,b; while(scanf("%d %d",&n,&m)==2 && (m||n)) { init(); while(m--) { scanf("%d %d",&a,&b); insert(a,b); insert(b,a); } tarjan(1,-1); printf("Case %d:\n",++cas); scanf("%d",&q); while(q--) { scanf("%d %d",&a,&b); if(f[a]!=f[b])//加这一句判断快了很多 LCA(a,b); printf("%d\n",sum); } puts(""); } return 0; }
题意:给定一个无向图,,进行Q次询问,对于每一次询问,给定起点跟终点,每一个点只能经过一次,问有多少个点是不可能经过的
分析:点数N 和边数M 都十分的庞大,,首先应该想到的就是缩点了,将每一个点双连通分量缩成一个点,问题是割点可以同时属于多个块,所以可以将割点再单独建立一个点,然后在块与割点直接连边,,得到的是一颗树,,这样隐约可以感觉到了,LCA 转RMQ ,,
具体可以这位大牛的博文http://blog.sina.com.cn/s/blog_7270d7f901017l3o.html
#include<iostream> #include<algorithm> #include<math.h> #include<stack> #include<vector> #include<stdio.h> #include<stdlib.h> #include<string.h> using namespace std; const int N = 100000+10; const int M = 400000+10; #pragma comment(linker, "/STACK:1024000000,1024000000") struct Edge { int v,nex; }E1[M],E2[M]; stack<int> st; int dfn[N],low[N],index,n,scc[N]; bool vis[N],cut[N]; int head1[N],size1,head2[N],size2; int f[N]; int dis[N],num,depth[N]; int dp[N*2][20],F[N*2],B[N*2],pos[N*2],bn,belong[N]; //F[]保存dfs产生的欧拉序列,dis保存每个节点到根的距离, //B[]保存与F[]对应的节点的深度,pos[]保存每一个节点在欧拉序列在第一次出现的位置 vector<int> g[N]; void init() { for(int i=0;i<n;++i) g[i].clear(); memset(head1,-1,sizeof(head1)); memset(head2,-1,sizeof(head2)); memset(vis,false,sizeof(vis)); memset(scc,0,sizeof(scc)); memset(cut,false,sizeof(cut)); memset(f,-1,sizeof(f)); memset(belong,-1,sizeof(belong)); size1=size2=index=num=0; } void insert1(int u,int v) { E1[size1].v=v; E1[size1].nex=head1[u]; head1[u]=size1++; } void insert2(int u,int v) { E2[size2].v=v; E2[size2].nex=head2[u]; head2[u]=size2++; } void tarjan(int u,int p) { dfn[u]=low[u]=index++; vis[u]=true; st.push(u); int v,son=0,flag=true; for(int i=head1[u];i!=-1;i=E1[i].nex) { v=E1[i].v; if(v==p && flag) { flag=false; continue; } if(!vis[v]) { tarjan(v,u); son++; low[u]=min(low[u],low[v]); if(dfn[u]<=low[v]) { int x; do { x=st.top(); st.pop(); g[num].push_back(x); }while(x!=v); g[num].push_back(u);//保存每一个块 num++; } if((p==-1 && son>1) || (p!=-1 && dfn[u]<=low[v])) cut[u]=true; } else low[u]=min(low[u],dfn[v]); } } void dfs(int cur,int deep,int len,int b) { vis[cur]=true; dis[cur]=len; depth[cur]=deep; F[bn]=cur; B[bn]=deep; pos[cur]=bn++; belong[cur]=b; for(int i=head2[cur];i!=-1;i=E2[i].nex) { int v=E2[i].v; if(!vis[v]) { dfs(v,deep+1,len+scc[v],b); F[bn]=cur; B[bn++]=deep; } } } void init_RMQ() { memset(dp,0,sizeof(dp)); for(int i=1;i<=bn;++i) dp[i][0]=i; for(int j=1;j<=log((double)(bn+1))/log(2.0);++j) { int limit=bn+1-(1<<j); for(int i=1;i<=limit;++i) { int x=dp[i][j-1]; int y=dp[i+(1<<(j-1))][j-1]; dp[i][j]=B[x]<B[y]?x:y; } } } int RMQ(int a,int b)//返回的是下标 { if(a>b){ swap(a,b); } int k=(int)(log((double)(b-a+1))/log(2.0)); int x=dp[a][k]; int y=dp[b+1-(1<<k)][k]; return B[x]<B[y]?x:y; } int main() { int m,q,cas=0; int a,b; while(scanf("%d %d",&n,&m)==2) { init(); for(int i=0;i<m;++i) { scanf("%d %d",&a,&b); insert1(a,b); insert1(b,a); } for(int i=0;i<n;++i) if(!vis[i]) tarjan(i,-1); int k=0; for(int i=0;i<n;++i) { if(cut[i]) { f[i]=k; scc[k]=1; k++; } } for(int i=0;i<num;++i) { for(int j=0;j<g[i].size();++j) { int v=g[i][j]; if(cut[v])//块与割点之间建边 { insert2(f[v],k); insert2(k,f[v]); } else f[v]=k; } scc[k]=g[i].size(); k++; } memset(vis,false,sizeof(vis)); bn=1; for(int i=0;i<k;++i) if(!vis[i]) dfs(i,0,scc[i],i); bn--; init_RMQ(); scanf("%d",&q); printf("Case #%d:\n",++cas); while(q--) { scanf("%d %d",&a,&b); if(a==b) { printf("%d\n",n-1); continue; } int u=f[a],v=f[b]; if(u==-1 || v==-1)//既不是割点也不是块 { printf("%d\n",n); continue; } if(belong[u]!=belong[v])//不连通,即不属于同一个连通分量 { printf("%d\n",n); continue; } int t=RMQ(pos[u],pos[v]); int tmp=dis[u]+dis[v]-2*dis[F[t]]+scc[F[t]]; int l=depth[u]+depth[v]-2*depth[F[t]]; printf("%d\n",n-(tmp-l)); } puts(""); } return 0; }