标签:
1、问矩阵连乘多少次可以每个值都大于0
矩阵乘法中有这样一个重要的步骤: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; }
给定一个有向图并规定:每两个点之间一定有边,同时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、等价性证明,白书例题。
#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、强连通分量入门题
#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; }
为王子喜欢的公主建边,由于后来给出的正解一定是成立的,那么后来的条件用来反向建边,那么跑完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; }
既然是强连通分量的题,很容易想到形成的东西是一坨一坨的,哈哈,然后如果某一坨入度为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; }
题意:给一个无向图。如果至少有两个环共用了一些边,那么这些边被认为是“冲突边”。如果一些边不在任何一个环中,这些边被认为是“多余边”。你要找出这个图中有多少“多余边”和“冲突边”然后输出条数。另外这图不一定是连通的
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; }
#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; } }
做法:先找出联通分量,判断每个联通分量是否是二分图,不在是二分图的点标记,其他点就是所求。注意割点的处理,每次在判断二分图之前都先为割顶染色,因为它会被染色多次。二分图的判断就是普通的染色法
#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; }
#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; }
如果有环的比如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; }
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、等价性证明 强联通分量基础题
派生到我的代码片 #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; }
#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; }
#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; }
#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; }
题意:已知点数、边数、割点的序号(一直以为是割点的个数,没法求了啊==),求是否能构成满足条件的图,输出
做法:首先要判断是否满足条件,我们需要找一个边数和点数的关系,点数的下限一定是等于边数的,点数的上限可以这么想:既然有一个点是割顶,那么把剩下的点分成两团,分别都是完全图,然后割顶是中间的“纽带”,那么想来这两团的个数相等的时候边数最多,上限可求。我们顺着这个思路就可以按顺序写出边啦~~
#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; }
题意:给定无向图,依次加一些边,求现有的桥的个数 做法:先对原始图求桥,我最开始的思路是不仅要求桥、也要求出每个点所在的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; }
两次都是遍历所有点,找一个数组用来储存所有点的子树个数(做法类似于寻找割点),遍历第一次计算剩余的团的个数,遍历第二次寻找删除的第二个点的子树最大值 ,求和
#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; }
标签:
原文地址:http://blog.csdn.net/zhou_yujia/article/details/51438266