复习:
有向图的连通性
强连通:如果两个点:u,v是互相达到的
无向图:联通 有向图:强连通
图中有多少SCC:暴力O(V^2+E)
kosaraju算法O(V+E):反图
(1)有向图G,建立反图rG,不会改变连通性
(2)对原图G做DFS,标记点的先后顺序,递归在最底层的点标记最小,回退过程中,其他点的优先级加大
(3)在反图RG上做DFS,从优先级大的开始做
Tarjan算法O(V+E):用栈分离不同的SCC
最先入栈的点:是这个SCC的祖先,他的num[]=low[]
比上面要快些
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=10010; const int INF=0x3fffffff; typedef long long LL; int cnt=0,dfn=0; int low[maxn],num[maxn]; int sccno[maxn],stack[maxn],top; //这个就表明属于哪个SCC vector<int> g[maxn]; void dfs(int u){ stack[top++]=u; low[u]=num[u]=++dfn; for(int i=0;i<g[u].size();i++){ int v=g[u][i]; if(!num[v]){ dfs(v); low[u]=min(low[u],low[v]); } else if(!sccno[v]){ //处理回退边 low[u]=min(low[u],num[v]); } } if(low[u]==num[u]){ //栈底的点是SCC的祖先, cnt++; while(1){ int v=stack[top--]; sccno[v]=cnt; if(u==v) break; //到达了祖先 } } } void tarjan(int n){ cnt=top=dfn=0; memset(sccno,0,sizeof(sccno)); memset(num,0,sizeof(num)); memset(low,0,sizeof(low)); for(int i=1;i<=n;i++){ if(!num[i]) dfs(i); } } int main(){ int n,m,u,v; while(scanf("%d %d",&n,&m),n!=0||m!=0){ for(int i=0;i<=n;i++){ g[i].clear(); } for(int i=0;i<m;i++){ scanf("%d %d",&u,&v); g[u].push_back(v); } tarjan(n); if(cnt==1) printf("Yes\n"); else printf("No\n"); } return 0; }
1513:【 例 1】受欢迎的牛
感觉很多题里面在缩点之后,是需要计算度的。
把每一个极大联通子图看成一个点(缩点),但后再组成一个图,这个图是没有环的在这个图中,至少存在1个出度为0的点,如果出度为0的点大于1,那么是无法确定的,但是如果出度为1的点只有1个,而且其他的点都能到达他的话,就可以确定答案就是那个点里面点数目
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=10050; const int INF=0x3fffffff; typedef long long LL; //有向图:极大连通子图 //把每一个极大联通子图看成一个点(缩点),但后再组成一个图,这个图是没有环的 //在这个图中,至少存在1个出度为0的点,如果出度为0的点大于1,那么是无法确定的 //但是如果出度为1的点只有1个,而且其他的点都能到达他的话,就可以确定 //答案就是那个点里面点数目 struct node{ int to,next; }ed[maxn*20]; int head[maxn],num[maxn],low[maxn]; int id[maxn],col[maxn]; //分别是每个点所属的极大联通分量,然后是这个连通分量的数量 int deg[maxn]; //这个极大联通子图的出度 bool inst[maxn]; //在不在栈里面 stack<int> st; int cnt,tot,nu,dfn; void add(int x,int y){ ed[++nu].to=y; ed[nu].next=head[x]; head[x]=nu; } void tarjin(int u){ num[u]=low[u]=++dfn; inst[u]=1; st.push(u); for(int i=head[u];i;i=ed[i].next){ int v=ed[i].to; if(!num[v]){ tarjin(v); low[u]=min(low[u],low[v]); } else if(inst[v]){ //有回退边 low[u]=min(low[u],num[v]); } } int k; if(low[u]==num[u]){ cnt++; do{ k=st.top(); st.pop(); inst[k]=0; //这里需要改变 id[k]=cnt; col[cnt]++; }while(k!=u); } } int main(){ int n,m; scanf("%d %d",&n,&m); for(int i=0;i<m;i++){ int x,y; scanf("%d %d",&x,&y); add(x,y); } for(int i=1;i<=n;i++){ if(!num[i]) tarjin(i); } for(int i=1;i<=n;i++){ for(int j=head[i];j;j=ed[j].next){ int u=ed[j].to; if(id[i]!=id[u]) deg[id[i]]++; //i这个极大连通子图的出度+1 } } for(int i=1;i<=cnt;i++){ if(!deg[i]){ if(tot) { printf("0\n");break; //因为只能有一个 } tot=i; } } printf("%d\n",col[tot]); return 0; }
1514:【例 2】最大半连通子图
//求极大半连通子图 极大半连通子图 极大半连通子图 极大半连通子图 极大半连通子图 极大半连通子图
// 此题题意就是:缩点后,求一条含有最多点数的DAG链,(这就是题中的最大半联通子图),并求出有多少个不同的最长链。
//主要有三个部分:1.缩点;2.DAG记忆化;3.离散化去重;(难点)4.链式前向星。
///思路:
//因为存在环,所以先缩点,缩完点以后,此时图就变成DAG,然后进行一遍dfs扫描,dfs时注意用记忆化,然后边扫边记录得到求最大值数组和求个数数组,最后统计答案即可。
//注意点:
//1.在求个数的时候要时刻取模,取模不能停!2.因为要求种类个数,所以要保证不重边(最大值与重边无关,但种类与重边有关)所以需要离散化
#include <bits/stdc++.h> using namespace std; const int N=1e5+5,M=1e6+5; int n,m,MOD,cnt,ans,MAX,k,now1,now2; int head[N],u[M],v[M]; //记录输入的u[]v[]是便于后面统计 int col,now,top,dfn[N],low[N],color[N],sta[N],si[N]; int rd[N],cd[N],dep[N],sum[N]; bool f[N]; struct edge{int next,from,to;}e[M]; struct node{int u,v;}C[M],b[M]; inline bool cmp(node a,node b){return (a.u<b.u || a.u==b.u && a.v<b.v|| a.u==b.u && a.v==b.v);} //排序,因为要离散化,去重 inline void add(int u,int v){cnt++;e[cnt].next=head[u];e[cnt].to=v;head[u]=cnt;} inline void tarjan(int u) { dfn[u]=low[u]=++now; sta[++top]=u; for (register int i=head[u]; i; i=e[i].next) { if (!dfn[e[i].to]) { tarjan(e[i].to); low[u]=min(low[u],low[e[i].to]); } else if (!color[e[i].to]) //还没有确定是哪个连通块,也就是代替了vis[]数组的作用 low[u]=min(low[u],dfn[e[i].to]); } if (low[u]==dfn[u]) { color[u]=++col; si[col]++; while (sta[top]!=u) color[sta[top]]=col,si[col]++,top--; top--; } } inline void dfs(int u,int fa) { f[u]=true; if (!cd[u]) {dep[u]=si[u]; sum[u]=1; return;} //出度为0,到了递归边界,开始会退,计算 //看出来了吗,这其实是回溯 //dep[]数组就是这条链能够拥有的节点数,sum[]数组是方案数 for (register int i=head[u]; i; i=e[i].next) if (e[i].to!=fa) { if (!f[e[i].to]) dfs(e[i].to,u); if (dep[e[i].to]+si[u]>dep[u]) dep[u]=dep[e[i].to]+si[u],sum[u]=sum[e[i].to]%MOD; else if (dep[e[i].to]+si[u]==dep[u]) sum[u]=(sum[u]+sum[e[i].to])%MOD; } } int main(){ memset(head,0,sizeof(head)); memset(dfn,0,sizeof(dfn)); scanf("%d%d%d",&n,&m,&MOD); for (register int i=1; i<=m; ++i) scanf("%d%d",&u[i],&v[i]),add(u[i],v[i]); //缩点 for (register int i=1; i<=n; ++i) if (!dfn[i]) tarjan(i); //离散化去重 for (register int i=1; i<=m; ++i) if (color[u[i]]!=color[v[i]]) k++,C[k].u=color[u[i]],C[k].v=color[v[i]]; //新边,但是还没有去重,之所以要这个是因为还需要排序 sort(C+1,C+k+1,cmp); //在这里排序 cnt=0; cnt++; b[cnt].u=C[1].u,b[cnt].v=C[1].v; now1=b[cnt].u,now2=b[cnt].v; for (register int i=2; i<=k; ++i) if (C[i].u!=now1|| C[i].v!=now2) cnt++,b[cnt].u=C[i].u,b[cnt].v=C[i].v,now1=b[cnt].u,now2=b[cnt].v; //这才是真正的边,没有重复的 //重新建图 memset(head,0,sizeof(head)); memset(e,0,sizeof(e)); for (register int i=1; i<=cnt; ++i) { cd[b[i].u]++,rd[b[i].v]++; e[i].next=head[b[i].u]; e[i].from=b[i].u; e[i].to=b[i].v; head[b[i].u]=i; //这里 } //记忆化搜索 for (register int i=1; i<=col; ++i) if (!rd[i] && !f[i]) dfs(i,0); //入度为0,且没有访问过 //统计答案 MAX=0,ans=0; for (register int i=1; i<=col; ++i) { if (dep[i]>MAX) { MAX=dep[i]; ans=sum[i]; } else if (MAX==dep[i]) ans=(ans+sum[i])%MOD; } printf("%d\n",MAX); printf("%d\n",ans); return 0; }
1515:网络协议
任务A: 需要求最多在多少学校发送新软件,其实就是求缩点后入度为0的个数(如果入度不为0就可以从其他学校传过来)
任务B:求入度为0的点数与出度为0的点的较大值。因为任务B要求在任意学校投放软件使得所有学校都能收到,所以很明显是需要整张图形成一个环,而环中所有节点入度和出度都不为0,所以需要把所有入度和出度的点度数增加。那么最少的方案就是把出度为0的点连上入度为0的点,所以任务B的答案就是入度为0与出度为0的点的较大值
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=1010; const int INF=0x3fffffff; typedef long long LL; /*任务A: 需要求最多在多少学校发送新软件,其实就是求缩点后入度为0的个数(如果入度不为0就可以从其他学校传过来) 任务B: 求入度为0的点数与出度为0的点的较大值。 因为任务B要求在任意学校投放软件使得所有学校都能收到,所以很明显是需要整张图形成一个环,而环中所有节点入度和出度都不为0,所以需要把所有入度和出度的点度数增加。 那么最少的方案就是把出度为0的点连上入度为0的点,所以任务B的答案就是入度为0与出度为0的点的较大值 */ int n; struct node{ int to,next; }edge[100000]; int head[110],num[110],low[110],vis[110]; int in[110],out[110]; int id[110],sta[110]; int cnt,col; void link(int a,int b){ cnt++; edge[cnt].to=b; edge[cnt].next=head[a]; head[a]=cnt; } int dfn,top; void tarjin(int u){ low[u]=num[u]=++dfn; sta[top++]=u; vis[u]=1; for(int i=head[u];i;i=edge[i].next){ int v=edge[i].to; if(!num[v]){ tarjin(v); low[u]=min(low[u],low[v]); } else if(!id[v]){ low[u]=min(low[u],num[v]); } } if(low[u]==num[u]){ col++; do{ top--; id[sta[top]]=col; }while(sta[top]!=u); } } int lin[100000][3]; int main(){ scanf("%d",&n); int x,k=0; for(int i=1;i<=n;i++){ scanf("%d",&x); while(x!=0){ link(i,x); k++; lin[k][1]=i; lin[k][2]=x; //记录下来 scanf("%d",&x); } } for(int i=1;i<=n;i++){ if(!num[i]) tarjin(i); } for(int i=1;i<=k;i++){ if(id[lin[i][1]]!=id[lin[i][2]]){ out[id[lin[i][1]]]++; in[id[lin[i][2]]]++; } } int ans1=0,ans2=0; for(int i=1;i<=col;i++){ if(in[i]==0) ans1++; if(out[i]==0) ans2++; } if(col==1) cout<<1<<endl<<0; else cout<<ans1<<endl<<max(ans2,ans1); return 0; }
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=10010; const int INF=0x3fffffff; typedef long long LL; int cnt=0,dfn=0; int low[maxn],num[maxn]; int sccno[maxn],stack[maxn],top;
//这个就表明属于哪个SCC vector<int> g[maxn]; void dfs(int u){ stack[top++]=u; low[u]=num[u]=++dfn; for(int i=0;i<g[u].size();i++){ int v=g[u][i]; if(!num[v]){ dfs(v); low[u]=min(low[u],low[v]); } else if(!sccno[v]){ //处理回退边 low[u]=min(low[u],num[v]); } } if(low[u]==num[u]){ //栈底的点是SCC的祖先, cnt++; while(1){ int v=stack[top--]; sccno[v]=cnt; if(u==v) break; //到达了祖先 } } } void tarjan(int n){ cnt=top=dfn=0; memset(sccno,0,sizeof(sccno)); memset(num,0,sizeof(num)); memset(low,0,sizeof(low)); for(int i=1;i<=n;i++){ if(!num[i]) dfs(i); } } int main(){ int n,m,u,v; while(scanf("%d %d",&n,&m),n!=0||m!=0){ for(int i=0;i<=n;i++){ g[i].clear(); } for(int i=0;i<m;i++){ scanf("%d %d",&u,&v); g[u].push_back(v); } tarjan(n); if(cnt==1) printf("Yes\n"); else printf("No\n"); } return 0; }