NC20603 [ZJOI2007]最大半连通子图
题目:
• 一个有向图G=(V,E)称为半连通的(Semi-Connected),如果满足:u,v∈V,满足u→v或v→u,即对于图中任意 两点u,v,存在一条u到v的有向路径或者从v到u的有向路径。
• 若G'=(V',E')满足V'∈V,E'是E中所有跟V'有关的边, 则称G'是G的一个导出子图。
• 若G'是G的导出子图,且G'半连通,则称G'为G的半连通子图。
• 若G'是G所有半连通子图 中包含节点数最多的,则称G'是G的最大半连通子图。
• 给定一个有向图G,请求出G的最大半连通子图拥有的节点数K ,以及不同的最大半连通子图
的数目C。由于C可能比较大,仅要求输出C对X的余数。
题解:
• 如果两点互相可以到达,那么它们必是半连通图,所以考虑先Tarjan缩点,接着去除重边重新建图,你会发现,在这个有向无环图(DAG)中,半连通子图都是一条链(可以举反例试试,这条链不可能有分支,否则将有两点无法抵达另一方)
• 于是,G的最大半连通子图拥有的节点数K就是最长链长度,不同的最大半连通子图的数目就是最长链个数。
• 最长链可以直接用拓扑排序(topo),最长链个数用一个类似DP的方法,用f【i】表示以 i为终点的方案数,那么f【i】就等于满足距离为起点到 i 的临时最短距离的点的 f 的和。然后查找距离等于最长链的点,答案为它们的方案数之和
代码:
#include<bits/stdc++.h> using namespace std; const int maxn=1e5+10,maxm=1e6+10; struct edge { int u,v,nxt; }e[maxm],ee[maxm]; int head[maxn],js,headd[maxn],jss; void addage(edge e[],int head[],int &js,int u,int v) { e[++js].u=u;e[js].v=v; e[js].nxt=head[u];head[u]=js; } int dfn[maxn],low[maxn],cnt,st[maxn],top,lt[maxn],lts,ltn[maxn]; void tarjan(int u) { dfn[u]=low[u]=++cnt; st[++top]=u; for(int i=head[u];i;i=e[i].nxt) { int v=e[i].v; if(!dfn[v]) { tarjan(v); low[u]=min(low[u],low[v]); } else if(!lt[v]) low[u]=min(low[u],dfn[v]); } if(dfn[u]==low[u]) { lt[u]=++lts;ltn[lts]++; while(st[top]!=u)lt[st[top--]]=lts,ltn[lts]++; --top; } } int n,m,x; int f[maxn],ff[maxn]; int cd[maxn],rd[maxn]; int maxd,maxf; int pc[maxn]; queue<int>q; void dfs() { while(!q.empty()) { int u=q.front();q.pop(); maxd=max(maxd,f[u]); for(int i=headd[u];i;i=ee[i].nxt) { int v=ee[i].v; rd[v]--; if(rd[v]==0)q.push(v); if(pc[v]==u)continue; if(f[u]+ltn[v]>f[v]) { f[v]=f[u]+ltn[v]; ff[v]=ff[u]; } else if(f[u]+ltn[v]==f[v]) { ff[v]=(ff[u]+ff[v])%x; } pc[v]=u; } } } int main() { scanf("%d%d%d",&n,&m,&x); for(int u,v,i=1;i<=m;++i) { scanf("%d%d",&u,&v); addage(e,head,js,u,v); } for(int i=1;i<=n;++i) if(!dfn[i])tarjan(i); for(int u=1;u<=n;++u) for(int i=head[u];i;i=e[i].nxt) if(lt[e[i].u]!=lt[e[i].v])addage(ee,headd,jss,lt[e[i].u],lt[e[i].v]),cd[lt[e[i].u]]++,rd[lt[e[i].v]]++; for(int i=1;i<=lts;++i) if(rd[i]==0)q.push(i),f[i]=ltn[i],ff[i]=1; dfs(); for(int i=1;i<=lts;++i) { if(f[i]==maxd)maxf=(maxf+ff[i])%x; } printf("%d\n%d\n",maxd,maxf); return 0; }