【一本通】强连通分量
学习Tarjan
受欢迎的牛
因为牛之间的关系具有传递性,所以找出有向图中的强连通子图,每个子图中的牛一定互相喜欢。
$Tarjan$缩点,只有出度为$0$的点(牛)才有可能受到其他所有牛的喜欢(图中已无环)。
如果出度为$0$的点不止$1$个,那么这几个子图中的牛都无法互相喜欢,输出$0$。
#include<bits/stdc++.h> using namespace std; const int N=1e4+5,M=5e4+5; int n,m,cnt,top,fro[N],s[N]; int tot,color,dfs[N],low[N],col[N],size[N],in[N]; struct edge{int to,nxt;}e[M]; void add(int x,int y) { e[++cnt]={y,fro[x]}; fro[x]=cnt; } void Tarjan(int u) { dfs[u]=low[u]=++tot; s[++top]=u; for(int i=fro[u];i;i=e[i].nxt) { int v=e[i].to; if(!dfs[v]) { Tarjan(v); low[u]=min(low[u],low[v]); } else if(!col[v]) low[u]=min(low[u],dfs[v]); } if(low[u]==dfs[u]) { col[u]=++color; while(s[top]!=u) { col[s[top--]]=color; size[color]++; } top--,size[color]++; } } int main() { scanf("%d%d",&n,&m); for(int i=1,a,b;i<=m;i++) scanf("%d%d",&a,&b),add(b,a); //反向建边,统计出度变为统计入度 for(int i=1;i<=n;i++) if(!dfs[i]) Tarjan(i); for(int i=1;i<=n;i++) for(int j=fro[i];j;j=e[j].nxt) if(col[i]!=col[e[j].to]) in[col[e[j].to]]++; int ans=-1; for(int i=1;i<=color;i++) if(!in[i]) ans=~ans?0:size[i]; printf("%d",ans); }
#include<bits/stdc++.h> using namespace std; const int N=3e3+5,M=8e3+5; int tot,top,color,s[N],dfn[N],low[N],col[N]; int n,p,r,cnt,ans,fro[N],money[N],mo[N],in[N]; struct edge{int to,nxt;}e[M]; void add(int x,int y) { e[++cnt]={y,fro[x]}; fro[x]=cnt; } void Tarjan(int u) { dfn[u]=low[u]=++tot; s[++top]=u; for(int i=fro[u];i;i=e[i].nxt) { int v=e[i].to; if(!dfn[v]) { Tarjan(v); low[u]=min(low[u],low[v]); } else if(!col[v]) low[u]=min(low[u],dfn[v]); } if(dfn[u]==low[u]) { col[u]=++color; mo[color]=money[u]; while(s[top]!=u) { col[s[top--]]=color; mo[color]=min(mo[color],money[s[top]]); } top--; } } int main() { scanf("%d%d",&n,&p); memset(money,0x7f,sizeof(money)); for(int i=1,u,m;i<=p;i++) { scanf("%d%d",&u,&m); money[u]=m; } scanf("%d",&r); for(int i=1,u,v;i<=r;i++) { scanf("%d%d",&u,&v); add(u,v); } for(int i=1;i<=n;i++) if(!dfn[i]&&money[i]<=2e4) Tarjan(i); for(int i=1;i<=n;i++) if(!dfn[i]) return printf("NO\n%d",i),0; for(int i=1;i<=n;i++) for(int j=fro[i];j;j=e[j].nxt) if(col[i]!=col[e[j].to]) in[col[e[j].to]]++; for(int i=1;i<=color;i++) if(!in[i]) ans+=mo[i]; printf("YES\n%d",ans); }
缩点后重新连边,此时答案为新图上最长链的个数。
用拓扑排序求出以各个点为终点的最长链的长度和个数,统计答案即可。
#include<bits/stdc++.h> using namespace std; const int N=1e5+5,M=1e6+5; int n,m,X,C,K,f[N],g[N],q[N],ru[N],vis[N]; int tot,top,color,s[N],dfn[N],low[N],co[N],size[N]; int cnt,fro[N],fro2[N]; struct edge{int to,nxt;}e[M],e2[M];
void add1(int x,int y) { e[++cnt]={y,fro[x]}; fro[x]=cnt; } void add(int x,int y) { e2[++cnt]={y,fro2[x]}; fro2[x]=cnt; ru[y]++; } void Tarjan(int u) { dfn[u]=low[u]=++tot; s[++top]=u; for(int i=fro[u];i;i=e[i].nxt) { 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(dfn[u]==low[u]) { co[u]=++color; while(s[top]!=u) { co[s[top--]]=color; size[color]++; } top--,size[color]++; } } void rebuild() { cnt=0; for(int i=1;i<=n;i++) for(int j=fro[i];j;j=e[j].nxt) if(co[i]!=co[e[j].to]) add(co[i],co[e[j].to]); } void DP() { int h=0,t=0; for(int i=1;i<=color;i++) { if(!ru[i]) q[t++]=i; f[i]=size[i],g[i]=1; } while(h!=t) { int u=q[h++],v; for(int i=fro2[u];i;i=e2[i].nxt) { ru[v=e2[i].to]--; if(!ru[v]) q[t++]=v; if(vis[v]==u) continue; //处理重边 if(f[u]+size[v]>f[v]) { f[v]=f[u]+size[v]; g[v]=g[u]; } else if(f[u]+size[v]==f[v]) g[v]=(g[v]+g[u])%X; vis[v]=u; } } } int main() { n=read(),m=read(),X=read(); for(int i=1;i<=m;i++) { int a=read(),b=read(); add1(a,b); } for(int i=1;i<=n;i++) if(!dfn[i]) Tarjan(i); rebuild(),DP(); for(int i=1;i<=color;i++) { if(f[i]>K) K=f[i],C=g[i]; else if(f[i]==K) C=(C+g[i])%X; } printf("%d\n%d",K,C); }
如有错误请指正。