复习:
有向图的连通性
强连通:如果两个点: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 "); else printf("No "); } 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; //这里需要改变,如果使用inst来判断在不在栈里面的话 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 ");break; //因为只能有一个 } tot=i; } } printf("%d ",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 ",MAX); printf("%d ",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; }
1516:消息的传递
我们的郭嘉大大在曹操这过得逍遥自在,但是有一天曹操给了他一个任务,在建邺城内有N(<=1000)个袁绍的奸细,将他们从1到N进行编号,同时他们之间存在一种传递关系,即若C[i,j]=1,则奸细i能将消息直接传递给奸细j。
现在曹操要发布一个假消息,需要传达给所有奸细,而我们的郭嘉大大则需要传递给尽量少的奸细使所有的奸细都知道这一个消息,问我们至少要传给几个奸细。
道题求SCC,求完了之后还有看还需要给几个人说,才能让所有人都知道,所以要计算出入度为0的SCC数量
求scc的时候用tarjin、kosaraju都可以
首先缩点后,然后还需要统计连通块中入度为0的个数,因为只需要传递给这些个,其他的连通块是可以到达的
不要以为进行DFS就可以了
#include<iostream> #include<cstdio> #include<stack> #include<vector> using namespace std; int dfn[1010],low[1010],visitime,scc,Belong[1010],n,ans; bool Instack[1010],ru[1010]; stack<int> s; vector<int> G[1010]; inline int _min(int a,int b) { if(a<b) return a; return b; } inline bool jiancha(int a,int b) { for(int i=0;i<G[a].size();i++) if(G[a][i]==b) return 1; return 0; } inline void Tarjan(int u) { s.push(u); Instack[u]=1; dfn[u]=low[u]=++visitime; for(int v=0;v<G[u].size();v++) { if(!dfn[G[u][v]]) { Tarjan(G[u][v]); low[u]=_min(low[u],low[G[u][v]]); } else if(dfn[G[u][v]]&&Instack[G[u][v]]) //处理回退边(连接到了已经访问过的点) { low[u]=_min(low[u],dfn[G[u][v]]); } } if(dfn[u]==low[u]) { scc++; int v; do { v=s.top(); s.pop(); Belong[v]=scc; Instack[v]=0;//出栈 } while(u!=v); } } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) { int a; scanf("%d",&a); if(a==1) G[i].push_back(j); } } for(int i=1;i<=n;i++) { if(dfn[i]==0) Tarjan(i); } for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) { if(Belong[i]!=Belong[j]&&jiancha(i,j)==1) //属于不同的连通块,但是彼此可达 { ru[Belong[j]]=1;//入度为1 } } } for(int i=1;i<=scc;i++) { if(ru[i]==0) //统计连通块中入度为0的个数 ans++; } printf("%d",ans); return 0; }
1517:间谍网络
#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=3010; const int INF=0x3fffffff; typedef long long LL; int n,p,r; int wei[maxn],low[maxn],num[maxn],col[maxn]; int cnt,dfn; int head[maxn]; int in[maxn]; struct node{ int to,next; }ed[10000]; int lin[10000][2]; void add(int a,int b){ cnt++; ed[cnt].next=head[a]; ed[cnt].to=b; head[a]=cnt; } int aas=0; stack<int> sst; void tarjin(int u){ low[u]=num[u]=++dfn; sst.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(!col[v]){ low[u]=min(low[u],num[v]); } } if(low[u]==num[u]){ aas++; int t; do{ t=sst.top(); sst.pop(); col[t]=aas; }while(t!=u); } } int main(){ scanf("%d %d",&n,&p); int pre,wan; memset(wei,-1,sizeof(wei)); for(int i=0;i<p;i++){ scanf("%d %d",&pre,&wan); wei[pre]=wan; } scanf("%d",&r); int x,y; for(int i=0;i<r;i++){ scanf("%d %d",&lin[i][0],&lin[i][1]); //记录这个边的端点 add(lin[i][0],lin[i][1]); } for(int i=1;i<=n;i++){ if(!num[i]) tarjin(i); } for(int i=0;i<r;i++){ if(col[lin[i][0]]!=col[lin[i][1]]){ //记录入度 in[col[lin[i][1]]]++; } } int op=0; int minn=INF,i; bool flag; for(i=1;i<=aas;i++){ if(in[i]==0) { //如果入度为0,就需要收买 minn=INF; flag=false; for(int j=1;j<=n;j++){ if(col[j]==i&&wei[j]!=-1){ //遍历所有的人,如果有人属于这个SCC并且可以被收买,那么就取这些人里面要钱最少的 minn=min(minn,wei[j]); flag=true; } } if(flag==false){ break; } op+=minn; } } if(flag) { printf("YES %d",op); } else{ printf("NO "); for(int j=1;j<=n;j++){ if(col[j]==i) { //就找到最小属于这个不能买的人 printf("%d",j); break; } } } return 0; }
1518:抢掠计划
思路是对的,但是我对缩点后的处理不是特别清楚,所以看了题解
首先我们看见可以重复走路径,那么就想到其实这些路口可以构成一个个强连通分量(因为只要我们能到达一个强连通分量其中某一个点,
那么我们就能到达这个强连通分量的其余所有点并且保证更优)
SPFA跑最长路(其实改个符号就好,没有必要建负权边跑最短路)
#include<queue> #include<cstdio> #include<cstring> #define N 500005 using namespace std; //邻接表存储/遍历(如果你这个都不会的话做这种综合大题有点早……先好好学图论吧) //SPFA跑最长路(其实改个符号就好,没有必要建负权边跑最短路)!!!!!1 //Tarjan缩点 struct edge{ int to,val,next; } e[N]; int m,n,p,s,cnt,g[N],u[N],v[N],w[N],hd[N],bar[N],dis[N],dfn[N],low[N],stk[N],sum[N]; bool vis[N]; queue <int> q; int ans=0,top=0,tot=0,total=0; inline int min(int a,int b){ return a<b?a:b; } void add(int u,int v){ cnt++; e[cnt].to=v; e[cnt].next=hd[u]; hd[u]=cnt; } void build(int u,int v,int w) //这个是为了后面跑最长路 { cnt++; e[cnt].to=v; e[cnt].val=w; e[cnt].next=hd[u]; hd[u]=cnt; } void clear() { cnt=0; memset(e,0,sizeof(e)); memset(hd,0,sizeof(hd)); } void readln() { clear(); scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) { scanf("%d%d",&u[i],&v[i]); add(u[i],v[i]); } for(int i=1;i<=n;i++) scanf("%d",&w[i]); scanf("%d%d",&s,&p); for(int i=1;i<=p;i++) scanf("%d",&bar[i]); } void Tarjan(int x) { dfn[x]=low[x]=++total; stk[++top]=x; vis[x]=true; //入栈 for(int i=hd[x];i;i=e[i].next) { int t=e[i].to; if(!dfn[t]) { Tarjan(t); low[x]=min(low[x],low[t]); } else if(vis[t]) low[x]=min(low[x],dfn[t]);//处理回退边 } if(dfn[x]==low[x]) { tot++; do{ int tp=stk[top]; sum[tot]+=w[tp]; //记录下这个连通块里面的点权和 vis[tp]=false; //退栈 g[tp]=tot; }while(stk[top--]!=x); } } void Spfa(int s) //跑路 { for(int i=1;i<=tot;i++) dis[i]=0; int gs=g[s]; q.push(gs); vis[gs]=true; dis[gs]=sum[gs]; //起点的距离为这个SCC中的点权和(能抢到的钱) while(!q.empty()) { int h=q.front(); q.pop(); vis[h]=false; for(int i=hd[h];i;i=e[i].next) { int t=e[i].to; if(dis[t]<dis[h]+e[i].val) { dis[t]=dis[h]+e[i].val; if(!vis[t]) { q.push(t); vis[t]=true; } } } } } int main() { readln(); for(int i=1;i<=n;i++) if(!dfn[i]) Tarjan(i); clear(); for(int i=1;i<=m;i++) if(g[u[i]]!=g[v[i]]) build(g[u[i]],g[v[i]],sum[g[v[i]]]); //建图!!用缩点后的点建图 Spfa(s); for(int i=1;i<=p;i++) ans=max(ans,dis[g[bar[i]]]); //在所有能去的酒吧里面找最大的值 printf("%d",ans); return 0; }
1519:和平委员会
这道题和2-SAT有点像,判断方法比较像:如果一个党派的两位成员都在,就不是合理的方案
而且建边:和合理对象建边
记得n*2
#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=40010; const int INF=0x3fffffff; typedef long long LL; int head[maxn],vis[maxn],low[maxn],num[maxn],col[maxn]; int ans,cnt,top,dfn,n,m; stack<int> st; struct node{ int t,next; }ed[maxn]; void add(int u,int v){ ed[++cnt].next=head[u]; ed[cnt].t=v; head[u]=cnt; } void tarjin(int u){ low[u]=num[u]=++dfn; st.push(u); vis[u]=1; for(int i=head[u];i;i=ed[i].next) { int v=ed[i].t; if(!num[v]){ tarjin(v); low[u]=min(low[u],low[v]);} else if(vis[v]) low[u]=min(low[u],num[v]); } if(low[u]==num[u]) { int v; ans++; do{ v=st.top(); st.pop(); vis[v]=0; col[v]=ans; } while(v!=u); } } int chan(int x){ return ((x%2)? x+1:x-1); //取这个党派的另一个成员 } int main(){ scanf("%d %d",&n,&m); int x,y; for(int i=0;i<m;i++){ scanf("%d %d",&x,&y); add(x,chan(y)); //与另一个可以的建立边 add(y,chan(x)); } for(int i=1;i<=(n<<1);i++){ //要乘2 if(!num[i]) tarjin(i); } bool flag=1; for(int i=1;i<=2*n;i++){ //每个党派有两个代表,所以要乘 2 if(i%2==1&&col[i]==col[i+1]) { flag=0; //这个其实也是2-SAT的判断成不成立方法,如果这个党派的两个成员都在的话,就不是一种合理的方案 break; } } if(!flag) printf("NIE "); else{ for(int i=1;i<=2*n;i++){ if(col[i]<col[chan(i)]){ //果委员会能以多种方法形成,程序可以只输出它们的某一个 printf("%d ",i); } } } return 0; }
Going from u to v or from v to u?(POJ2762)3601
给你一个有n个点m条边有向图,让你判断该图是否对于任意的一对顶点(X,Y),存在从X到Y的路径或者从Y到X的路径。
单连通!!!
虽然本题是求单连通的,但是我们需要先求强连通分量,因为,强连通分量中存在双向路径,因此可以缩点,缩点后就好处理多了。
如果要满足题意,缩点后的树必须是一条链,而且所有边的方向都是一样的,如果出现分支,很容易证明会出现不可到达的一对点。
那么剩下的就是求最长链的顶点数是否等于强连通分量的个数了。
那么就可以使用拓扑排序,或者直接DFS。
拓扑排序中,不能出现同时有两个点的入度为0。
DFS从入度为0的顶点开始搜,是求出深搜的层数,因为单链的话层数是等于顶点数的
https://blog.csdn.net/iteye_6233/article/details/82270727 但是最后有个点过不了,不知道为什么TAT
#include <iostream> #include <map> #include <cstdio> #include <stack> #include <cstring> #include <algorithm> #define MAXN 10005 #define MAXM 100005 #define INF 1000000000 using namespace std; int n, m; int scc;//强连通分量 int indexx;//每个节点的dfs访问次序编号 int dfn[MAXN];//标记结点i的dfs访问次序 int low[MAXN];//记录节点u或u的子树中的所有节点的最小标号 int fa[MAXN];//属于哪个分支 bool instack[MAXN];//是否在栈中 int in[MAXN], head[MAXN], e; int out[MAXN];//出度 stack <int>s; int tmp; int vis[MAXN]; struct Edge { int v, next; }edge[MAXM]; void insert(int x, int y) { edge[e].v = y; edge[e].next = head[x]; head[x] = e++; } void tarjan(int u) { dfn[u] = low[u] = ++indexx; s.push(u); instack[u] = true; for (int j = head[u]; j != -1; j = edge[j].next) { int v = edge[j].v; if(dfn[v] == 0)//未曾访问过 { 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]) { scc++; while(1) { int tmp = s.top(); s.pop(); instack[tmp] = 0; fa[tmp] = scc; if(tmp == u) break; } } } void init() { scc = indexx = 0; memset(dfn, 0, sizeof(dfn)); memset(instack, 0, sizeof(instack)); e = 0; memset(head, -1, sizeof(head)); memset(in, 0, sizeof(in)); memset(out, 0, sizeof(out)); } void dfs(int v, int deep) { tmp = max(tmp, deep); vis[v] = 1; for(int i = head[v]; i != -1; i = edge[i].next) if(!vis[edge[i].v]) { if(fa[edge[i].v] == fa[v]) dfs(edge[i].v, deep); else dfs(edge[i].v, deep + 1); } } void solve() { for (int i = 1;i <= n; i++) { if (!dfn[i]) tarjan(i); } for (int i = 1;i <= n; i++) { for(int j = head[i]; j != -1; j = edge[j].next) { int u = fa[i]; int v = fa[edge[j].v]; if(u != v) { out[u]++; in[v]++; } } } tmp = 0; memset(vis, 0, sizeof(vis)); for(int i = 1; i <= n; i++) if(in[fa[i]] == 0) { dfs(i, 1); break; } printf(tmp == scc ? "Yes " : "No "); } int main() { int T, x, y; scanf("%d", &T); while(T--) { init(); scanf("%d%d", &n, &m); while(m--) { scanf("%d%d", &x, &y); insert(x, y); } solve(); } return 0; }
1573 -- 【图的连通性】上白泽慧音(classroom)2916
第1行:两个正整数N,M。
第2..M+1行:每行三个正整数a,b,t, t = 1表示存在从村庄a到b的单向道路,t = 2表示村庄a,b之间存在双向通行的道路。保证每条道路只出现一次。
第1行: 1个整数,表示最大的绝对连通区域包含的村庄个数。
第2行:若干个整数,依次输出最大的绝对连通区域所包含的村庄编号。
#include<iostream> #include<cstdio> #include<cstring> using namespace std; const int maxn=2e5+7; const int inf=0x3f3f3f3f; struct note{ int to,nex; }e[maxn*3]; int col,num,head[maxn],dfn[maxn],low[maxn],de[maxn],co[maxn],si[maxn],stk[maxn]; int top,n,m; int cnt=-1; void add(int x,int y) { static int cnt=0; cnt++; e[cnt].to=y; e[cnt].nex=head[x]; head[x]=cnt; } void tarjan(int u) { dfn[u]=low[u]=++num; stk[++top]=u; for(int i=head[u];i;i=e[i].nex) { int v=e[i].to; if(!dfn[v]){ tarjan(v); low[u]=min(low[u],low[v]); } else if(!co[v])low[u]=min(low[u],dfn[v]); } if(low[u]==dfn[u]) { co[u]=++col; ++si[col]; while(stk[top]!=u) { ++si[col]; co[stk[top]]=col; --top; } --top; } } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) { int x,y,f; cin>>x>>y>>f; if(f==1)add(x,y); if(f==2)add(x,y),add(y,x); } for(int i=1;i<=n;i++)if(!dfn[i])tarjan(i); for(int i=1;i<=col;i++)cnt=max(cnt,si[i]); cout<<cnt<<endl; for(int i=1;i<=n;i++) { if(si[co[i]]==cnt) { int now=co[i]; for(int j=i;j<=n;j++)if(co[j]==now)cout<<j<<" "; return 0; } } return 0; }
1574 -- 【图的连通性】校园网2413
子任务a:请编一个程序,根据学校间支援协议(各个学校的支援名单),计算最少需要将一个新软件直接提供给多少个学校,才能使软件通过网络被传送到所有学校。
子任务b:如果允许在原有支援协议上添加新的支援关系。则总可以形成一个新的协议,使得此时只需将一个新软件提供给任何一个学校,其他所有学校就都可以通过网络获得该软件。编程计算最少需要添加几条新的支援关系。
分析:
任务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; }