[bzoj1093] [ZJOI2007] 最大半连通子图
Description
一个有向图 \(G=(V,E)\) 称为半连通的(\(Semi-Connected\)),如果满足:$ \forall u,v \in V$,满足 \(u→v\) 或 \(v→u\),即对于图中任意两点 \(u\),\(v\) ,存在一条 \(u\) 到 \(v\) 的有向路径或者从 \(v\) 到 \(u\) 的有向路径。若 \(G'=(V',E')\) 满足 \(E'\) 是 \(E\) 中所有跟 \(V'\) 有关的边,则称 \(G'\) 是 \(G\) 的一个导出子图。若 \(G'\) 是 \(G\) 的导出子图,且 \(G'\) 半连通,则称 \(G'\) 为 \(G\) 的半连通子图。若 \(G'\) 是 \(G\) 所有半连通子图中包含节点数最多的,则称 \(G'\) 是 \(G\) 的最大半连通子图。给定一个有向图 \(G\) ,请求出 \(G\) 的最大半连通子图拥有的节点数 \(K\),以及不同的最大半连通子图的数目 \(C\)。由于 \(C\) 可能比较大,仅要求输出 \(C\) 对 \(X\) 的余数。
Input
第一行包含两个整数 \(N\) ,\(M\),\(X\) 。\(N\),\(M\) 分别表示图 \(G\) 的点数与边数,\(X\) 的意义如上文所述接下来 \(M\) 行,每行两个正整数 \(a\),$ b$,表示一条有向边 \((a, b)\) 。图中的每个点将编号为 \(1,2,3…N\),保证输入中同一个 \((a,b)\) 不会出现两次。\(N \leq 100000\),$ M \leq 1000000$;对于 \(100 \%\) 的数据, \(X \leq 10^8\)
Output
应包含两行,第一行包含一个整数 \(K\) 。第二行包含整数 $ C$ \(Mod\) \(X\).
Sample Input
6 6 20070603
1 2
2 1
1 3
2 4
5 6
6 4
Sample Output
3
3
题解
一道不错的题。
“半连通子图”看上去有些棘手,那先 \(tarjan\) 缩环,把原先的有向图变成 \(DAG\)
然后稍微推一下会发现,半连通子图相当于 \(DAG\) 中的一条链,我们要求的就是 \(DAG\) 中的最长链及其个数
\(dp\) 一下就好了。
代码
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 100005;
struct node{
int v;
node *next;
}pool[N*10],*h[N],pool2[N*10],*h2[N];
int cnt,cnt2;
void addedge(int u,int v){
node *p=&pool[++cnt];
p->v=v;p->next=h[u];h[u]=p;
}
int n,m,X;
int dfn[N],low[N],tot,vis[N];
int st[N],top,scc[N],sccno,size[N];
void dfs(int u){
int v;
dfn[u]=low[u]=++tot;
st[top++]=u;
vis[u]=1;
for(node *p=h[u];p;p=p->next)
if(!vis[v=p->v]){
dfs(v);
low[u]=min(low[u],low[v]);
}
else if(vis[v]==1) low[u]=min(low[u],dfn[v]);
if(low[u]==dfn[u]){
sccno++;
for(;;){
size[sccno]++;
vis[v=st[--top]]=2;
scc[v]=sccno;
if(u==v) break;
}
}
}
struct edge{
int u,v;
bool operator < (const edge &b) const{
if(scc[u]!=scc[b.u]) return scc[u]<scc[b.u];
return scc[v]<scc[b.v];
}
}ed[N*10];
void addedge2(int u,int v){
node *p=&pool2[++cnt2];
p->v=v;p->next=h2[u];h2[u]=p;
}
int ml[N],mt[N];
void dp(int u){
if(ml[u]!=-1) return;
int v;
ml[u]=size[u]; mt[u]=1;
for(node *p=h2[u];p;p=p->next){
dp(v=p->v);
if(ml[u]<ml[v]+size[u]) ml[u]=ml[v]+size[u],mt[u]=mt[v];
else if(ml[u]==ml[v]+size[u]) mt[u]=(mt[u]+mt[v])%X;
}
}
int main()
{
scanf("%d%d%d",&n,&m,&X);
for(int i=0;i<m;i++)
scanf("%d%d",&ed[i].u,&ed[i].v),addedge(ed[i].u,ed[i].v);
for(int i=1;i<=n;i++)
if(!dfn[i]) dfs(i);
sort(ed,ed+m);
for(int i=0;i<m;i++){
if(i!=0 && scc[ed[i].u]==scc[ed[i-1].u] && scc[ed[i].v]==scc[ed[i-1].v])
continue;
if(scc[ed[i].u]==scc[ed[i].v]) continue;
addedge2(scc[ed[i].u],scc[ed[i].v]);
}
memset(ml,-1,sizeof(ml));
for(int i=1;i<=n;i++)
if(ml[i]==-1) dp(i);
int ans=0,t=0;
for(int i=1;i<=n;i++)
if(ml[i]>ans) ans=ml[i],t=mt[i];
else if(ml[i]==ans) t=(t+mt[i])%X;
printf("%d\n%d\n",ans,t);
return 0;
}