1、问矩阵连乘多少次可以每个值都大于0
cf402EStrictly Positive Matrix【tarjan前向星模板、矩阵】
矩阵乘法中有这样一个重要的步骤:a^k中a[i][j]如果是+说明从i点有正好走k步就可以到达j点的路(那么由于子环的存在>k的步数的路也存在)即i、j连通我们依次建边所有点对(大于0的)剩下的跑一边tarjan
强连通分量的个数=1,则输出"yes"反之“no"
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int M=4e6+10,N=4e6+10; int head[N],ed; struct node{ int to,next; }edge[M]; int sta[N],vis[N],low[N],dfn[N],top; //,out[N] void init(){ ed=0; memset(head,-1,sizeof(head)); memset(edge,0,sizeof(edge)); memset(sta,0,sizeof(sta)); } void addedge(int a,int b){ edge[ed].to=b; edge[ed].next=head[a]; head[a]=ed++; } void tarbfs(int k,int cnt,int &num){ vis[k]=1; low[k]=cnt; dfn[k]=cnt; sta[top++]=k; for(int i=head[k];i>-1;i=edge[i].next){ if(vis[edge[i].to]==0) tarbfs(edge[i].to,++cnt,num); if(vis[edge[i].to]==1) low[k]=min(low[k],low[edge[i].to]); } if(dfn[k]==low[k]){ ++num; while(top>0&&sta[top]!=k){ top--; low[sta[top]]=num; vis[sta[top]]=2; }// } } int tarjan(int n){ int num=0,cnt=1; top=0; memset(vis,0,sizeof(vis)); memset(low,0,sizeof(low)); for(int i=1;i<=n;i++){ if(vis[i]==0) tarbfs(i,cnt,num); } return num; } int main() { // freopen("cin.txt","r",stdin); int n,m; while(cin>>n){ int a,b; init(); for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) { scanf("%d",&a); if(i==j) continue; if(a>0) addedge(i,j); } } int num=tarjan(n); if(num==1) puts("YES"); else puts("NO"); } return 0; }
2、判断是否存在三角恋
hdu4324 Triangle LOVE【tarjan强连通分量基础题】
给定一个有向图并规定:每两个点之间一定有边,同时A指向B则B定不能指向A,反之亦然。 询问是否存在仅有三个点构成的环。
首先判断有向图中是否存在环马上有tarjan能够很好的解决。并且如果存在大于三个点以上的环的话肯定存在仅有三个点构成的环。 因为每两个点之间都有边,并且只有一个给定的指向,画几个图便可以推导出这样的结论。
证明: 假设存在一个n元环,因为a->b有边,b->a必定没边,反之也成立 所以假设有环上三个相邻的点a-> b-> c,那么如果c->a间有边,就已经形成了一个三元环,如果c->a没边,那么a->c肯定有边,这样就形成了一个n-1元环。。。。 所以只需证明n为4时一定有三元环即可,显然成立。
所以知道在tarjan后或中判断是否存在大于等于三个点的强连通图————转自acdreamer博客讲解
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int M=4e6+10,N=2e3+10; int head[N],ed; char str[2006][2006]; struct node{ int to,next; }edge[M]; int sta[N],vis[N],low[N],dfn[N],top; //,out[N] void init(){ ed=0; memset(head,-1,sizeof(head)); memset(edge,0,sizeof(edge)); memset(sta,0,sizeof(sta)); } void addedge(int a,int b){ edge[ed].to=b; edge[ed].next=head[a]; head[a]=ed++; } void tarbfs(int k,int cnt,int &num){ vis[k]=1; low[k]=cnt; dfn[k]=cnt; sta[top++]=k; for(int i=head[k];i>-1;i=edge[i].next){ if(vis[edge[i].to]==0) tarbfs(edge[i].to,++cnt,num); if(vis[edge[i].to]==1) low[k]=min(low[k],low[edge[i].to]); } if(dfn[k]==low[k]){ ++num; while(top>0&&sta[top]!=k){ //printf("%d ",sta[top]); top--; low[sta[top]]=num; vis[sta[top]]=2; }// } } int tarjan(int n){ int num=0,cnt=1; top=0; memset(vis,0,sizeof(vis)); memset(low,0,sizeof(low)); for(int i=1;i<=n;i++){ if(vis[i]==0) tarbfs(i,cnt,num); } return num; } int main() { // freopen("cin.txt","r",stdin); int n,m,t,b; scanf("%d",&t); b=1; while(t--){ int a; init(); scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%s",str[i]+1); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(str[i][j]=='1') addedge(i,j); /*for(int i=1;i<=ed;i++) { for(int k=head[i];k!=-1;k=edge[k].next) printf("u=%d v=%d ",i,edge[k].to); printf("\n"); }*/ int num=tarjan(n); //printf("%d ",num); printf("Case #%d: ",b++); if(num>=1&&num!=n) puts("Yes"); else puts("No"); } return 0; }
3、等价性证明,白书例题。
hdu2767Proving Equivalences【STL版SCCTarjan+缩点】(有注释)
说白了就是问加多少边使得原图联通,跑强连通分量之后缩点,出度=0或者入度=0的最大值就是答案
#include <iostream> #include<cstdio> #include<stack> #include<vector> #include<cstring> #include<algorithm> using namespace std; #define maxn 20005 vector<int>G[maxn]; int pre[maxn],lowlink[maxn],sccno[maxn],dfs_clock,scc_cnt; stack<int>S; int in0[maxn],out0[maxn]; void dfs(int u) { pre[u]=lowlink[u]=++dfs_clock; S.push(u);<span style="font-family: Arial, Helvetica, sans-serif;">//每搜索到一个点,压入栈中</span> for(int i=0;i<G[u].size();i++)//遍历与p相连的点 { int v=G[u][i]; if(!pre[v])//不在栈中 { dfs(v); lowlink[u]=min(lowlink[u],lowlink[v]); } else if(!sccno[v])//在栈中 lowlink[u]=min(lowlink[u],pre[v]); } if(lowlink[u]==pre[u])//发现一个根 { scc_cnt++; for(;;) { int x=S.top();S.pop();//词典以上的所有点全部出栈 构成一个强连通分量 sccno[x]=scc_cnt;//scc_cnt是强连通分量的序号 if(x==u) break; } } } void find_scc(int n) { dfs_clock=scc_cnt=0; memset(sccno,0,sizeof(sccno)); memset(pre,0,sizeof(pre)); for(int i=0;i<n;i++) if(!pre[i]) dfs(i); } int main() { int T,n,m; scanf("%d",&T); while(T--) { scanf("%d%d",&n,&m); for(int i=0;i<n;i++) G[i].clear(); for(int i=0;i<m;i++) { int u,v; scanf("%d%d",&u,&v);u--;v--; G[u].push_back(v); } find_scc(n); for(int i=1;i<=scc_cnt;i++) in0[i]=out0[i]=1; for(int u=0;u<n;u++) { for(int i=0;i<G[u].size();i++) { int v=G[u][i]; if(sccno[u]!=sccno[v]) in0[sccno[v]]=out0[sccno[u]]=0; } } int a=0,b=0; for(int i=1;i<=scc_cnt;i++) { if(in0[i]) a++; if(out0[i]) b++; } int ans=max(a,b); if(scc_cnt==1) ans=0; printf("%d\n",ans); } return 0; }
4、强连通分量入门题
poj2553The Bottom of a Graph【tarjan中SCC出度是1】
#include <iostream> #include<cstdio> #include<stack> #include<vector> #include<cstring> #include<algorithm> using namespace std; #define maxn 5005 vector<int>G[maxn]; int pre[maxn],lowlink[maxn],sccno[maxn],dfs_clock,scc_cnt; stack<int>S; int in0[maxn],out0[maxn]; int num[5006]; void dfs(int u) { pre[u]=lowlink[u]=++dfs_clock; S.push(u); for(int i=0;i<G[u].size();i++) { int v=G[u][i]; if(!pre[v]) { dfs(v); lowlink[u]=min(lowlink[u],lowlink[v]); } else if(!sccno[v]) lowlink[u]=min(lowlink[u],pre[v]); } if(lowlink[u]==pre[u]) { scc_cnt++; // printf("序号是%d ,里面有",scc_cnt); for(;;) { int x=S.top();S.pop(); sccno[x]=scc_cnt; //printf("%d ",x); if(x==u) break; } } } void find_scc(int n) { dfs_clock=scc_cnt=0; memset(sccno,0,sizeof(sccno)); memset(pre,0,sizeof(pre)); for(int i=0;i<n;i++) if(!pre[i]) dfs(i); } int main() { // freopen("cin.txt","r",stdin); int n,m; while(~scanf("%d",&n)) { if(n==0) break; scanf("%d",&m); for(int i=0;i<n;i++) G[i].clear(); for(int i=0;i<m;i++) { int u,v; scanf("%d%d",&u,&v);u--;v--; G[u].push_back(v); } find_scc(n); for(int i=1;i<=scc_cnt;i++) in0[i]=out0[i]=1; // printf("scc_cnt=%d ",scc_cnt); for(int u=0;u<n;u++) { for(int i=0;i<G[u].size();i++) { int v=G[u][i]; if(sccno[u]!=sccno[v]) in0[sccno[v]]=out0[sccno[u]]=0; } } int a=0,cnt=0; for(int i=1;i<=scc_cnt;i++) { if(out0[i]) { // printf("i=%d ",i); for(int j=0;j<n;j++) { if(sccno[j]==i) num[cnt++]=j+1; } } } //printf("cnt=%d ",cnt); if(cnt) { sort(num,num+cnt); printf("%d",num[0]); for(int i=1;i<cnt;i++) printf(" %d",num[i]); } printf("\n"); } return 0; }
5、强连通分量公主王子配对问题
poj1904King‘s Quest【SCC tarjan解决配对问题】
给出每个王子喜欢的公主,要求王子选择某个公主后,其他王子也都能选择喜欢的公主,并且给出了一组正解
为王子喜欢的公主建边,由于后来给出的正解一定是成立的,那么后来的条件用来反向建边,那么跑完tarjan,强连通分量内的彼此都是可行解(就是说,如果输出其中一对,其他的也可以满足条件)。如果满足”王子喜欢这个公主“的条件,那么就可以输出
#include <iostream> #include<cstdio> #include<stack> #include<vector> #include<cstring> #include<algorithm> using namespace std; #define maxn 2005 vector<int>G[maxn<<1]; vector<int>ans; int pre[maxn<<1],lowlink[maxn<<1],sccno[maxn<<1],dfs_clock,scc_cnt; stack<int>S; int ccount[maxn<<1]; bool exist[maxn<<1][maxn<<1]; void dfs(int u) { pre[u]=lowlink[u]=++dfs_clock; S.push(u); for(int i=0;i<G[u].size();i++) { int v=G[u][i]; if(!pre[v]) { dfs(v); lowlink[u]=min(lowlink[u],lowlink[v]); } else if(!sccno[v]) lowlink[u]=min(lowlink[u],pre[v]); } if(lowlink[u]==pre[u]) { scc_cnt++; // printf("序号是%d ,里面有",scc_cnt); for(;;) { int x=S.top();S.pop(); sccno[x]=scc_cnt; // ccount[scc_cnt]++; // printf("%d ",x+1); if(x==u) break; } } } void find_scc(int n) { dfs_clock=scc_cnt=0; memset(sccno,0,sizeof(sccno)); memset(pre,0,sizeof(pre)); for(int i=0;i<n;i++) if(!pre[i]) dfs(i); } int main() { // freopen("cin.txt","r",stdin); int n,k,a; while(~scanf("%d",&n)) { for(int i=0;i<n;i++) G[i].clear(); // memset(ccount,0,sizeof(ccount)); memset(exist,false,sizeof(exist)); for(int i=0;i<n;i++) { scanf("%d",&k); while(k--) { scanf("%d",&a); a--; G[i].push_back(a+2000); exist[i][a+2000]=true; } } for(int i=0;i<n;i++) { scanf("%d",&a); a--; G[a+2000].push_back(i); exist[a+2000][i]=true; } find_scc(n); /********** for(int i=0;i<n;i++) { printf("count=%d",ccount[sccno[i]]/2); for(int j=2000;j<2000+n;j++) { if(sccno[i]==sccno[j]) printf(" %d",j-2000+1); } printf("\n"); } **********/ for(int i=0;i<n;i++) { ans.clear(); for(int j=2000;j<2000+n;j++) { if(exist[i][j]&&sccno[i]==sccno[j]) ans.push_back(j-2000); } printf("%d",ans.size()); for(int j=0;j<ans.size();j++) printf(" %d",ans[j]+1); printf("\n"); } } return 0; }
6、强联通分量水题
hdu1827Summer Holiday【tarjan强连通分量解决最小联系费用】
既然是强连通分量的题,很容易想到形成的东西是一坨一坨的,哈哈,然后如果某一坨入度为0,那么很不幸,这一坨只能直接被威士忌通知,至于具体通知这一坨中的哪一个,枚举一遍就知道了,最后把话费求和~
#include <iostream> #include<cstdio> #include<stack> #include<vector> #include<cstring> #include<algorithm> using namespace std; #define maxn 2020 vector<int>G[maxn]; int pre[maxn],lowlink[maxn],sccno[maxn],dfs_clock,scc_cnt; stack<int>S; int in0[maxn],out0[maxn]; int value[maxn]; int cost; void dfs(int u) { pre[u]=lowlink[u]=++dfs_clock; S.push(u); for(int i=0;i<G[u].size();i++) { int v=G[u][i]; if(!pre[v]) { dfs(v); lowlink[u]=min(lowlink[u],lowlink[v]); } else if(!sccno[v]) lowlink[u]=min(lowlink[u],pre[v]); } if(lowlink[u]==pre[u]) { scc_cnt++; for(;;) { int x=S.top();S.pop(); sccno[x]=scc_cnt; if(x==u) break; } } } void find_scc(int n) { dfs_clock=scc_cnt=0; memset(sccno,0,sizeof(sccno)); memset(pre,0,sizeof(pre)); for(int i=0;i<n;i++) if(!pre[i]) dfs(i); } int main() { int T,n,m; while(scanf("%d%d",&n,&m)!=EOF) { for(int i=0;i<n;i++) G[i].clear(); cost=0; for(int i=0;i<n;i++) scanf("%d",&value[i]); for(int i=0;i<m;i++) { int u,v; scanf("%d%d",&u,&v);u--;v--; G[u].push_back(v); } find_scc(n); for(int i=1;i<=scc_cnt;i++) in0[i]=out0[i]=1; for(int u=0;u<n;u++) { for(int i=0;i<G[u].size();i++) { int v=G[u][i]; if(sccno[u]!=sccno[v]) in0[sccno[v]]=out0[sccno[u]]=0; } } int a=0,b=0; for(int i=1;i<=scc_cnt;i++) { if(in0[i]) { a++; int maxvalue=0x3f3f3f3f; for(int j=0;j<n;j++) { if(sccno[j]==i) maxvalue=min(maxvalue,value[j]); } cost+=maxvalue; } } printf("%d %d\n",a,cost); } return 0; }
7、双联通分量
hdu3394Railway【双连通分量+模板详细解释】
题意:给一个无向图。如果至少有两个环共用了一些边,那么这些边被认为是“冲突边”。如果一些边不在任何一个环中,这些边被认为是“多余边”。你要找出这个图中有多少“多余边”和“冲突边”然后输出条数。另外这图不一定是连通的
1。“多余边”不在任何一个环中,那么多余边一定是桥,所以统计这个无向图中有多少桥即可(这个好求)
2。对于每个联通分量中如果边数大于点数,那么所有的边都算冲突边(在原来模板上加入这样的话:将同一个双联通分量的所有边输入到同一个vector当中,这样方便求边数,也方便求点数)
#include <iostream> #include <cstdio> #include <cstring> #include <vector> #include <stack> using namespace std; const int N=10006; struct Edge { int st, en; Edge() {} Edge(int a, int b) { st=a, en=b; } }; stack <Edge> palm; vector <int> arc[N]; vector <Edge> block[N]; int dfn[N], low[N]; bool vs[N]; int n, m, ind, T, sum1, sum2; void tarjan(int u, int pre) { dfn[u]=low[u]=T++; int len=(int)arc[u].size(); for(int i=0; i<len; i++) { int v=arc[u][i]; if(dfn[v]==-1)//遍历与此点相连而且没遍历过的点 { palm.push(Edge(u, v));//边压入栈 tarjan(v, u); if(low[u]>low[v]) low[u]=low[v];//用子节点的low值更新自己的(low的定义不就是此点以及其后代所能连回的最先祖先的pre值嘛) if(dfn[u]<=low[v])//存在子节点连不回此点之前的点则这个点是割顶 d=====( ̄▽ ̄*)b --定理 { for(Edge temp; !palm.empty(); ) { temp=palm.top();// if(dfn[temp.st]<dfn[v]) break; block[ind].push_back(temp), palm.pop(); } block[ind++].push_back(Edge(u, v));//最后一个压入这个序号为ind的边就是u,v palm.pop(); if(dfn[u]<low[v]) sum1++;//作为割顶的特殊情况 如果v的后代只能连回v自己 那么构成的是桥 } } else if(v!=pre && dfn[v]<dfn[u]) { palm.push(Edge(u, v)); if(low[u]>dfn[v]) low[u]=dfn[v]; } } } int main() { while(scanf("%d%d", &n, &m), n!=0 || m!=0) { for(int i=0; i<n; i++) arc[i].clear(); for(int i=0, a, b; i<m; i++) { scanf("%d%d", &a, &b); arc[a].push_back(b); arc[b].push_back(a); } for(int i=0; i<n; i++) dfn[i]=-1, block[i].clear(); while(!palm.empty()) palm.pop(); ind=T=sum1=sum2=0; for(int i=0; i<n; i++) if(dfn[i]==-1) tarjan(i, -1); for(int i=0; i<ind; i++) { for(int j=0; j<n; j++) vs[j]=0; int len=(int)block[i].size(), tot=0; for(int j=0; j<len; j++) { if(!vs[block[i][j].st]) vs[block[i][j].st]=1, tot++; if(!vs[block[i][j].en]) vs[block[i][j].en]=1, tot++; } if(len>tot) sum2+=len; } printf("%d %d\n", sum1, sum2); } return 0; }
8、双联通分量输出桥
hdu3849By Recognizing These Guys, We Find Social Networks Useful【map+双连通分量求桥+扩栈代码】
#include<iostream> #include<algorithm> #include<string> #include<cstdio> #include<cstring> #include<map> #define N 10005 #define M_M 200005 using namespace std; #pragma comment(linker, "/STACK:102400000,102400000") int top,bcnt; int stack[N],indx; int dfn[N],low[N],cn; map<string,int> M; //存地点对应的编号 map<int,string> MM; //存编号对应的地点 map<string,int> final; //存每条边的编号,很有用的! struct node{ int next,v; node(){}; node(int a,int b){ next=a,v=b; } }E[M_M]; struct ans{ string s; int ind; }ret[M_M]; //存最后结果,ind拿来排序的时候用 int head[N],NE; int n,m; void init(){ M.clear(); MM.clear(); final.clear(); NE=0;bcnt=0;top=0;indx=0;cn=0; memset(head,-1,sizeof(head)); memset(dfn,0,sizeof(dfn)); memset(low,0,sizeof(low)); } void insert(int u,int v){ E[NE]=node(head[u],v); head[u]=NE++; } void tarjan(int u,int pre){ //----------------------------------1 dfn[u]=low[u]=++indx; stack[top++]=u; for(int i=head[u];i!=-1;i=E[i].next){ int v=E[i].v; if(v==pre) continue; if(!dfn[v]){ tarjan(v,u); if(low[v]<low[u]) low[u]=low[v]; if(low[v]>dfn[u]){ //满足割边要求 ret[cn].s=MM[u]+' '+MM[v]; if(!final[ret[cn].s]) //-----------------------2 ret[cn].s=MM[v]+' '+MM[u]; ret[cn].ind=final[ret[cn].s]; cn++; } } else if(dfn[v]<low[u]) low[u]=dfn[v]; } } bool cmp(ans x,ans y){ return x.ind<y.ind; } int bin[N]; int find(int x){ if(bin[x]==x) return bin[x]; return bin[x]=find(bin[x]); } bool merge(int x,int y){ int fx=find(x); int fy=find(y); if(fx!=fy){ bin[fx]=fy; return true; } return false; } int main(void){ //freopen("cin.txt","r",stdin); int t; scanf("%d",&t); while(t--){ scanf("%d%d",&n,&m); init(); int ind=1; for(int i=0;i<=n;i++) bin[i]=i; for(int i=1;i<=m;i++){ string u,v; cin>>u>>v; if(!M[u]){ M[u]=ind++; MM[ind-1]=u; } if(!M[v]){ M[v]=ind++; MM[ind-1]=v; } final[u+' '+v]=i; int k1=M[u],k2=M[v]; insert(k1,k2); insert(k2,k1); if(merge(k1,k2)) bcnt++; } if(bcnt!=n-1){ printf("0\n"); continue; } tarjan(1,-1); sort(ret,ret+cn,cmp); printf("%d\n",cn); for(int i=0;i<cn;i++) cout<<ret[i].s<<endl; } }
9、经典题
poj2942圆桌骑士【点双连通分量+二分图判断】
题意:n个骑士举行圆桌会议,每次圆桌至少3人参加而且是奇数,互相憎恶的不坐旁边,统计多少个骑士不可能参加任何一个会议
做法:先找出联通分量,判断每个联通分量是否是二分图,不在是二分图的点标记,其他点就是所求。注意割点的处理,每次在判断二分图之前都先为割顶染色,因为它会被染色多次。二分图的判断就是普通的染色法
#include <iostream> #include<cstdio> #include<cstring> #include<vector> #include<stack> #include<algorithm> using namespace std; #define maxn 1005 struct Edge { int u,v; }; int pre[maxn],iscut[maxn],bccno[maxn],dfs_clock,bcc_cnt;//pre[]表示开始时间 bccno[]表示某点所在集合号 bcc_cnt表示编号 vector<int>G[maxn],bcc[maxn]; stack<Edge>S; int dfs(int u,int fa) { int lowu=pre[u]=++dfs_clock;//记录访问时间 int child=0; for(int i=0;i<G[u].size();i++)//遍历与u点相连接的边 { int v=G[u][i]; Edge e=(Edge){u,v};//当前的点所连接的边 if(!pre[v]) { S.push(e); child++; int lowv=dfs(v,u);//后代 lowu=min(lowu,lowv);//用后代的low函数更新自己 if(lowv>=pre[u])//存在子节点连不回此点之前的点,此点是割点--定理 { iscut[u]=true;//标记为割点 bcc_cnt++; bcc[bcc_cnt].clear(); for(;;) { Edge x=S.top();S.pop(); if(bccno[x.u]!=bcc_cnt) { bcc[bcc_cnt].push_back(x.u); bccno[x.u]=bcc_cnt; } if(bccno[x.v]!=bcc_cnt)//防止加重 { bcc[bcc_cnt].push_back(x.v); bccno[x.v]=bcc_cnt; } if(x.u==u&&x.v==v) break; } } } else if(pre[v]<pre[u]&&v!=fa)//访问过v 而且v在u之前 { S.push(e); lowu=min(lowu,pre[v]);//用反向边更新自己 } } if(fa<0&&child==1) iscut[u]=0;//判断是根节点而且只有一个孩子那么不是割顶 return lowu;//返回后代序号 } void find_bcc(int n){ memset(pre,0,sizeof(pre)); memset(iscut,0,sizeof(iscut)); memset(bccno,0,sizeof(bccno)); dfs_clock=bcc_cnt=0; for(int i=0;i<n;i++) if(!pre[i]) dfs(i,-1); } int odd[maxn],color[maxn]; bool bipartite(int u,int b)//判断是否是二分图 { for(int i=0;i<G[u].size();i++)//遍历与这个点相连接的点 { int v=G[u][i]; if(bccno[v]!=b) continue;//不在同一个连通分量 跳过 if(color[v]==color[u]) return false; if(!color[v])//此点未遍历过 { color[v]=3-color[u];//此点颜色等于三减去与此点相连点的颜色 if(!bipartite(v,b)) return false; } } return true; } int A[maxn][maxn]; int main() { int kase=0,n,m; while(scanf("%d%d",&n,&m)==2&&n) { for(int i=0;i<n;i++) G[i].clear(); memset(A,0,sizeof(A)); for(int i=0;i<m;i++) { int u,v; scanf("%d%d",&u,&v); u--;v--; A[u][v]=A[v][u]=1; } for(int u=0;u<n;u++) { for(int v=u+1;v<n;v++) if(!A[u][v]) { G[u].push_back(v); G[v].push_back(u); } } find_bcc(n); memset(odd,0,sizeof(odd)); //题目要求不在任何一个简单奇圈上的节点个数 for(int i=1;i<=bcc_cnt;i++) { memset(color,0,sizeof(color)); for(int j=0;j<bcc[i].size();j++) bccno[bcc[i][j]]=i;//主要处理割顶 int u=bcc[i][0]; color[u]=1;//u是这个连通分量的第一个点 if(!bipartite(u,i))//如果某个连通分量不是二分图 for(int j=0;j<bcc[i].size();j++) odd[bcc[i][j]]=1;//给其中所有节点标记在奇圈上 } int ans=n; for(int i=0;i<n;i++) if(odd[i]) ans--; printf("%d\n",ans); } return 0; }
10、
poj3177Redundant Paths【构造双连通分量:并查集缩点 模板】
加几条边可以构成双连通分量?构造双连通分量的加边数=(原图的叶节点数+1)/2,先用并查集缩点,把所有当前的双连通分量都缩到一起,然后就构成了只有桥的图,枚举每个桥,记录每个点的次数,每次加一。只有1的点就是原图的叶结点
#include <iostream> #include <cstdio> #include <cstring> #include <vector> #include <stack> using namespace std; const int N=5006; vector<int>G[N]; struct bridge { int u,v; }bg[2*N]; int vis[N],low[N],dfn[N],Time; int fa[N],deg[N]; int n,m,cnt; void init() { for(int i=0;i<n;i++) G[i].clear(); memset(dfn,0,sizeof(dfn)); memset(low,0,sizeof(low)); memset(vis,0,sizeof(vis)); memset(deg,0,sizeof(deg)); for(int i=1;i<=n;i++) fa[i]=i; cnt=Time=0; } int findset(int x) { if(x!=fa[x]) fa[x]=findset(fa[x]); return fa[x]; } void Tarjan(int u,int father) { low[u] = dfn[u] = ++Time; vis[u] = 1; for(int i=0;i<G[u].size();i++) { int v = G[u][i]; if(v == father) continue; if(!vis[v]) { Tarjan(v,u); low[u] = min(low[u],low[v]); if(low[v] > dfn[u]) //u->v为桥 bg[cnt].u = u,bg[cnt++].v = v; else //否则,u,v同属一个连通分量,合并 { int fx = findset(u); int fy = findset(v); if(fx != fy) fa[fx] = fy; } } else low[u] = min(low[u],dfn[v]); } } int main() { // freopen("cin.txt","r",stdin); while(~scanf("%d%d", &n, &m)) { init(); for(int i=0;i<m;i++) { int u,v; scanf("%d%d",&u,&v); G[u].push_back(v); G[v].push_back(u); } Tarjan(1,-1); for(int i=0;i<cnt;i++) { int fx=findset(bg[i].u); int fy=findset(bg[i].v); deg[fx]++; deg[fy]++; } int leaf=0; for(int i=1;i<=n;i++) if(deg[i]==1) leaf++; printf("%d\n",(leaf+1)/2); } return 0; }
11、改造双向联通图成为单向联通图
poj1515Street Directions【无向图->有向图 链式前向星版tarjan求桥】
如果有环的比如a->b->c->a则一定可以变为有向,方向就是搜索的方向:a->b,b->c,c->a
即:在同一个双联通分量的边输出单向,桥输出双向
#include <iostream> #include <cstdio> #include <cstring> #include <vector> #include <stack> using namespace std; const int MAXN=1111; struct Edge { int v,next; bool vis; }edge[MAXN*MAXN]; int n,m,NE; int head[MAXN]; void Insert(int u,int v) { edge[NE].v=v; edge[NE].next=head[u]; edge[NE].vis=false; head[u]=NE++; } int cnt,_count; int low[MAXN],dfn[MAXN],color[MAXN]; bool mark[MAXN]; stack<int>S; void Tarjan(int u,int father) { int flag=0; low[u]=dfn[u]=++cnt; mark[u]=true; S.push(u); for(int i=head[u];i!=-1;i=edge[i].next) { int v=edge[i].v; if(v==father&&!flag) { flag=1; continue; } if(dfn[v]==0) { Tarjan(v,u); low[u]=min(low[u],low[v]); } else if(mark[v]) low[u]=min(low[u],dfn[v]); } if(low[u]==dfn[u]) { int v; _count++; do{ v=S.top(); S.pop(); mark[v]=false; color[v]=_count;//标记每个点所在的集合序号 }while(u!=v); } } void Solve(int u,int father) { for(int i=head[u];i!=-1;i=edge[i].next) { int v=edge[i].v; if(v==father) continue; if(color[u]==color[v]&&!edge[i].vis) printf("%d %d\n",u,v);//因为在同一个连通分量中 双向的边输出一个方向的就好 else if(color[u]!=color[v]&&!edge[i].vis) { printf("%d %d\n",u,v); printf("%d %d\n",v,u);//因为是桥 所以输出双向的 } edge[i].vis=true; edge[i^1].vis=true;//不管是输出了单向的还是双向的 这条边都结束了 if(!mark[v]) { mark[v]=true; Solve(v,u); } } } int main() { // freopen("cin.txt","r",stdin); int u,v,t=1; while(~scanf("%d%d",&n,&m)) { if(n==0&&m==0) break; NE=0; memset(head,-1,sizeof(head)); while(m--) { scanf("%d%d",&u,&v); Insert(u,v); Insert(v,u); } cnt=_count=0; memset(dfn,0,sizeof(dfn)); memset(mark,false,sizeof(mark)); Tarjan(1,-1); printf("%d\n\n",t++); memset(mark,false,sizeof(mark)); mark[1]=true; Solve(1,-1); puts("#"); } return 0; }
12、混合图改有向图 这个题需要自己写dfs代码
poj1438One-way Traffic【双连通分量:混合图->有向图】
1) 第一次深搜时把所有有向边都当成无向边,这时候求出的桥必须双向都输出(当然了,一种给的数据一定也是双向的)
2)第二次深搜时输出这三种:
1)之前没输出过:
发现是双向的边 而且是桥 ==>输出相反的顺序(因为双连通分量必须内部相互可达,所以一定没有桥,遇到桥了,说明走反了)
发现是双向的边 而且不是桥==>就按着这个顺序顺出
2)之前遍历过:而且是双向的 直接按着这个顺序输出就好
#include<iostream> #include<cstdio> #include<cstring> #include<string> #include<algorithm> #include<map> #include<queue> #include<set> #include<stack> #include<cmath> #include<vector> #define inf 0x3f3f3f3f #define Inf 0x3FFFFFFFFFFFFFFFLL #define eps 1e-9 #define pi acos(-1.0) using namespace std; typedef long long ll; const int maxn=2000+10; const int maxm=maxn*maxn; int pre[maxn]; int dfs_clock,n,m; bool mz[maxn][maxn],flag[maxn][maxn]; void Init() { memset(flag,0,sizeof(flag)); memset(mz,0,sizeof(mz)); memset(pre,0,sizeof(pre)); dfs_clock=0; } void AddEdge(int u,int v,bool f) { mz[u][v]=true; flag[u][v]=f; } int Tarjan(int u,int fa) { int lowu=pre[u]=++dfs_clock; for(int i=1;i<=n;i++) { int v=i; if(!flag[u][v]||(v==fa)) continue; if(!pre[v]) { int lowv=Tarjan(v,u); lowu=min(lowu,lowv); if(lowv>pre[u]) { printf("%d %d 2\n",u,v); flag[u][v]=false;flag[v][u]=false; continue; } } else lowu=min(lowu,pre[v]); //之前访问过了,所以用反向边更新自己 } return lowu; } int dfs(int u,int fa) { int lowu=pre[u]=++dfs_clock; for(int i=1;i<=n;i++) { int v=i; if(!flag[u][v]||(v==fa)) continue; if(!pre[v]) { int lowv=dfs(v,u); lowu=min(lowu,lowv); if(flag[v][u]) { if(lowv>pre[u]) { printf("%d %d 1\n",v,u); flag[u][v]=false;flag[v][u]=false; } else { printf("%d %d 1\n",u,v); flag[u][v]=false;flag[v][u]=false; } } } else { lowu=min(lowu,pre[v]); //之前访问过了,所以用反向边更新自己 if(flag[v][u]) { printf("%d %d 1\n",u,v); flag[u][v]=false; } } } return lowu; } int main() { //freopen("cin.txt","r",stdin); //freopen("out.txt","w",stdout); int u,v,type; scanf("%d%d",&n,&m); Init(); for(int i=0;i<m;++i) { scanf("%d%d%d",&u,&v,&type); AddEdge(u,v,true); AddEdge(v,u,type==2); } Tarjan(1,-1); memset(pre,0,sizeof(pre)); dfs_clock=0; for(int i=1;i<=n;++i) { if(!pre[i]) dfs(i,-1); } return 0; }
13、等价性证明 强联通分量基础题
HDU 3836 - Equivalent Sets【强连通分量 基础题】
派生到我的代码片 #include<cstdio> #include<cstring> #include<vector> #include<stack> using namespace std; #define maxn 20000 vector<int>G[maxn]; int pre[maxn],lowlink[maxn],sccno[maxn],dfs_clock,scc_cnt; stack<int>S; int min(int a,int b){if(a<b)return a;return b;} void dfs(int u) { pre[u]=lowlink[u]=++dfs_clock; S.push(u); for(int i=0;i<G[u].size();i++) { int v=G[u][i]; if(!pre[v]) { dfs(v); lowlink[u]=min(lowlink[u],lowlink[v]); } else if(!sccno[v]) lowlink[u]=min(lowlink[u],pre[v]); } if(lowlink[u]==pre[u]) { scc_cnt++; for(;;) { int x=S.top();S.pop(); sccno[x]=scc_cnt; if(x==u) break; } } } void find_scc(int n) { dfs_clock=scc_cnt=0; memset(sccno,0,sizeof(sccno)); memset(pre,0,sizeof(pre)); for(int i=0;i<n;i++) if(!pre[i]) dfs(i); } int in0[maxn],out0[maxn]; int main() { int T,n,m; while(~scanf("%d%d",&n,&m)) { for(int i=0;i<n;i++) G[i].clear(); for(int i=0;i<m;i++) { int u,v; scanf("%d%d",&u,&v); u--;v--; G[u].push_back(v); } find_scc(n); for(int i=1;i<=scc_cnt;i++)in0[i]=out0[i]=1; for(int u=0;u<n;u++) { for(int i=0;i<G[u].size();i++) { int v=G[u][i]; if(sccno[u]!=sccno[v])in0[sccno[v]]=out0[sccno[u]]=0; } } int a=0,b=0; for(int i=1;i<=scc_cnt;i++) { if(in0[i])a++; if(out0[i]) b++; } int ans=a; if(ans<b) ans=b; if(scc_cnt==1) ans=0; printf("%d\n",ans); }return 0; }
14
hdu4635Strongly connected 【求最多加多少边仍不是强连通分量】
对于一个有n个节点的完全图而言,他有n*n个边,对于这个图而言,最终的结果必然是只有两个集合(即缩点之后的团)团内是scc无环,彼此之间只有单向的通道,设两个团的节点个数分别为x,y则最终可加的边数是n*n-n-x*y-m,找最大 正解是遍历找出入度或者出度=0的团选一个最大的。为什么不是尽量凑出一个接近n/2的团?可能是无法操作吧
#include <iostream> #include<cstdio> #include<stack> #include<vector> #include<cstring> #include<algorithm> using namespace std; #define maxn 200005 vector<int>G[maxn]; int pre[maxn],lowlink[maxn],sccno[maxn],dfs_clock,scc_cnt; stack<int>S; int in0[maxn],out0[maxn]; void dfs(int u) { pre[u]=lowlink[u]=++dfs_clock; S.push(u);//<span style="font-family: Arial, Helvetica, sans-serif;">//每搜索到一个点,压入栈中</span> for(int i=0;i<G[u].size();i++)//遍历与p相连的点 { int v=G[u][i]; if(!pre[v])//不在栈中 { dfs(v); lowlink[u]=min(lowlink[u],lowlink[v]); } else if(!sccno[v])//在栈中 lowlink[u]=min(lowlink[u],pre[v]); } if(lowlink[u]==pre[u])//发现一个根 { scc_cnt++; for(;;) { int x=S.top();S.pop();//词典以上的所有点全部出栈 构成一个强连通分量 sccno[x]=scc_cnt;//scc_cnt是强连通分量的序号 if(x==u) break; } } } void find_scc(int n) { dfs_clock=scc_cnt=0; memset(sccno,0,sizeof(sccno)); memset(pre,0,sizeof(pre)); for(int i=0;i<n;i++) if(!pre[i]) dfs(i); } int num[100000]; int main() { // freopen("cin.txt","r",stdin); int T,n,m,cas=1; scanf("%d",&T); while(T--) { scanf("%d%d",&n,&m); for(int i=0;i<n;i++) G[i].clear(); memset(num,0,sizeof(num)); for(int i=0;i<m;i++) { int u,v; scanf("%d%d",&u,&v); u--;v--; G[u].push_back(v); } find_scc(n); for(int i=1;i<=scc_cnt;i++) in0[i]=out0[i]=0; for(int u=0;u<n;u++) { for(int i=0;i<G[u].size();i++) { int v=G[u][i]; if(sccno[u]!=sccno[v]) in0[sccno[v]]=out0[sccno[u]]=1; } num[sccno[u]]++; } int maxnum=100000,maxtot; for(int i=1;i<=scc_cnt;i++) { //printf("i=%d,num=%d,in=%d,out=%d\n",i,num[i],in0[i],out0[i]); if(in0[i]==0||0==out0[i]) { if(num[i]<maxnum) { maxnum=num[i]; maxtot=i; } } } long long ans=n*n*1LL-n-1LL*maxnum*(n-maxnum)-m; if(scc_cnt!=1&&ans>=0) printf("Case %d: %lld\n",cas++,ans); else printf("Case %d: -1\n",cas++); } return 0; }
15、乱搞题
CodeForces 22E Scheme【变成强联通图至少增加多少边并输出】
我们用深搜来找环,in0[]=0相当于是最后的“尾巴”,对于这个点深搜的结果是这个环的终点,终点链接起点
#include <iostream> #include<cstdio> #include<stack> #include<vector> #include<cstring> #include<algorithm> using namespace std; #define maxn 200005 vector<int>G[maxn],st,ed; int in0[maxn]; int col[maxn]; int dfs(int p) { col[p]=1; for(int i=0;i<G[p].size();i++) { int v=G[p][i]; if(!col[v])return col[p]=dfs(v); return col[p]=p; } } int main() { // freopen("cin.txt","r",stdin); int n; // scanf("%d",&T); while(~scanf("%d",&n)) // while(T--) { //scanf("%d%d",&n,&m); for(int i=0;i<=n;i++) G[i].clear(); memset(in0,0,sizeof(in0)); memset(col,0,sizeof(col)); for(int i=1;i<=n;i++) { int v; scanf("%d",&v); in0[v]++; G[i].push_back(v); } int k=0,t=0; for(int i=1;i<=n;i++) { if(!in0[i]) { k++; st.push_back(i); ed.push_back(dfs(i)); } } t=k; for(int i=1;i<=n;i++) { if(!col[i]) { k++; st.push_back(i); ed.push_back(dfs(i)); } } if(t==0&&k==1)k=0; printf("%d\n",k); for(int i=0;i<k;i++) { printf("%d %d\n",ed[i],st[(i+1)%k]); } } return 0; }
16
poj 1144 Network【无向图求割顶模板题】
#include <iostream> #include<cstdio> #include<cstring> #include<vector> #include<algorithm> using namespace std; #define maxn 200 int pre[maxn],iscut[maxn],dfs_clock,low[maxn]; vector<int>G[maxn]; int dfs(int u,int fa) { int lowu=pre[u]=++dfs_clock; int child=0; for(int i=0;i<G[u].size();i++) { int v=G[u][i]; if(!pre[v]) { child++; int lowv=dfs(v,u); lowu=min(lowu,lowv); if(lowv>=pre[u]) iscut[u]=true; } else if(pre[v]<pre[u]&&v!=fa) lowu=min(lowu,pre[v]); } if(fa<0&&child==1)iscut[u]=0; low[u]=lowu; return lowu; } int main() { // freopen("cin.txt","r",stdin); int n; while(~scanf("%d",&n)&&n) { char str[200]; memset(pre,0,sizeof(pre)); memset(iscut,0,sizeof(iscut)); for(int i=1;i<=n;i++)G[i].clear(); dfs_clock=0; getchar(); while(gets(str)) { if(str[0]=='0')break; int i,u=0,len=strlen(str); //printf("len=%d\n",len); for(i=0;i<len;i++) { if(str[i]==' ')break; u+=(str[i]-'0'); u*=10; } i++; u/=10; // printf("u=%d\n",u); int tmp=0; for(;i<len;i++) { if(str[i]==' ') { tmp/=10; // printf("tmp=%d\n",tmp); G[u].push_back(tmp); G[tmp].push_back(u); tmp=0; i++; } tmp+=(str[i]-'0'); tmp*=10; if(i==len-1) { tmp/=10; //printf("tmp=%d\n",tmp); G[u].push_back(tmp); G[tmp].push_back(u); break; } } } for(int i=1;i<=n;i++) { if(!pre[i])dfs(i,-1); //if(!G[i].empty())for(int j=0;j<G[i].size();j++)printf("i=%d,j=%d\n",i,G[i][j]); } int num=0; for(int i=1;i<=n;i++) if(iscut[i])num++; printf("%d\n",num); } return 0; }
17 乱搞题
codeforces22c System Administrator【给定一个割顶输出边 BCC】
题意:已知点数、边数、割点的序号(一直以为是割点的个数,没法求了啊==),求是否能构成满足条件的图,输出
做法:首先要判断是否满足条件,我们需要找一个边数和点数的关系,点数的下限一定是等于边数的,点数的上限可以这么想:既然有一个点是割顶,那么把剩下的点分成两团,分别都是完全图,然后割顶是中间的“纽带”,那么想来这两团的个数相等的时候边数最多,上限可求。我们顺着这个思路就可以按顺序写出边啦~~
#include <iostream> #include<cstdio> using namespace std; int n,m,v; int main() { scanf("%d%d%d",&n,&m,&v); { if(m<n-1||m>(n*n-3*n+4)/2) printf("-1\n"); else { int la; for(int i=1;i<=n;i++) { if(i!=v) { printf("%d %d\n",i,v); la=i; // break; } } m-=(n-1); for(int i=1;i<la&&m;i++) { for(int j=i+1;j<la&&m;j++) { if(i!=v&&j!=v) { printf("%d %d\n",i,j); m--; } } } } } return 0; }
18
hdu2460Network【双连通分量求桥 在线求lca】
题意:给定无向图,依次加一些边,求现有的桥的个数 做法:先对原始图求桥,我最开始的思路是不仅要求桥、也要求出每个点所在的BCC序号,加入新边的时候根据缩点的结果找了多少个桥,然而并不需要缩点,缩点的话还要表示bcc之间的关系再建边,好麻烦的说。正确做法就只是用lca找新加入两点之间的路径,遇到桥就修改bool变量、桥数减一。
#include <iostream> #include<cstdio> #include<cstring> #include<vector> #include<algorithm> using namespace std; vector<int>G[100009]; int n,m,dfs_clock,bridgenum; int fa[100009],isbridge[100009],pre[100009]; bool mark[100009]; void init() { for(int i=0;i<=n;i++) G[i].clear(); bridgenum=dfs_clock=0; memset(pre,0,sizeof(pre)); for(int i=1;i<=n;i++)fa[i]=i; memset(isbridge,0,sizeof(isbridge)); } int dfs(int u,int father) { int lowu=pre[u]=++dfs_clock; // printf("u=%d,pre=%d,fa=%d\n",u,pre[u],father); for(int i=0;i<G[u].size();i++) { int v=G[u][i]; if(!pre[v]) { fa[v]=u; int lowv=dfs(v,u); lowu=min(lowu,lowv); if(lowv>pre[u]) { isbridge[v]=true; bridgenum++; } } else if(pre[v]<pre[u]&&v!=father) lowu=min(lowu,pre[v]); } return lowu; } void lca(int u,int v) { while(pre[u]>pre[v]) { if(isbridge[u]) { isbridge[u]=0; bridgenum--; } u=fa[u]; // printf("u=%d,bridge=%d\n",u,bridgenum); } while(pre[u]<pre[v]) { if(isbridge[v]) { isbridge[v]=0; bridgenum--; } v=fa[v]; // printf("v=%d,bridge=%d\n",v,bridgenum); } while(u!=v) { if(isbridge[u]) { isbridge[u]=0; bridgenum--; } if(isbridge[v]) { isbridge[v]=0; bridgenum--; } u=fa[u];v=fa[v]; } } int main() { // freopen("cin.txt","r",stdin); // freopen("out.txt","w",stdout); int cas=1,qq; while(~scanf("%d%d",&n,&m)) { if(n==0&&m==0)break; init(); while(m--) { int u,v; scanf("%d%d",&u,&v); G[u].push_back(v); G[v].push_back(u); } dfs(1,-1); printf("Case %d:\n",cas++); scanf("%d",&qq); while(qq--) { int u,v; scanf("%d%d",&u,&v); lca(u,v); printf("%d\n",bridgenum); } puts(""); } return 0; }
19
hdu4587TWO NODES【割点】
题意:已知无向图选两个点点从原图中删掉,剩下的最大连通分量个数?
两次都是遍历所有点,找一个数组用来储存所有点的子树个数(做法类似于寻找割点),遍历第一次计算剩余的团的个数,遍历第二次寻找删除的第二个点的子树最大值 ,求和
#pragma comment(linker, "/STACK:102400000000,102400000000") #include <iostream> #include<cstdio> #include<cstring> #include<vector> #include<algorithm> using namespace std; #define maxn 5009 vector<int>G[maxn]; int pre[maxn],dfs_cnt,iscut[maxn]; int n,m,none; void init() { for(int i=0;i<n;i++)G[i].clear(); none=n; } int dfs(int u,int fa) { int lowu=pre[u]=++dfs_cnt; int child=0; for(int i=0;i<G[u].size();i++) { int v=G[u][i]; if(v==none) continue; if(!pre[v]) { child++; int lowv=dfs(v,u); lowu=min(lowu,lowv); if(lowv>=pre[u]) iscut[u]++; } else if(pre[v]<pre[u]&&v!=fa) lowu=min(lowu,pre[v]); } if(fa<0&&child==1)iscut[u]=0; return lowu; } int solve(int x) { int ans=0,left=0; memset(iscut,0,sizeof(iscut)); dfs_cnt=0; memset(pre,0,sizeof(pre)); none=x; for(int i=0;i<n;i++) if(i!=x&&!pre[i]) iscut[i]--,left++,dfs(i,-1); for(int i=0;i<n;i++) if(i!=x) ans=max(ans,iscut[i]+1); ans+=left-1; return ans; } int main() { // freopen("cin.txt","r",stdin); while(~scanf("%d%d",&n,&m)) { init(); while(m--) { int u,v; scanf("%d%d",&u,&v); G[u].push_back(v); G[v].push_back(u); } int ans=0; for(int i=0;i<n;i++) ans=max(ans,solve(i)); printf("%d\n",ans); } return 0; }