BZOJ 1093: [ZJOI2007]最大半连通子图
Time Limit: 30 Sec Memory Limit: 162 MB
Submit: 4068 Solved: 1638
[Submit][Status][Discuss]
Description
一个有向图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的余数。
Input
第一行包含两个整数N,M,X。N,M分别表示图G的点数与边数,X的意义如上文所述接下来M行,每行两个正整
数a, b,表示一条有向边(a, b)。图中的每个点将编号为1,2,3…N,保证输入中同一个(a,b)不会出现两次。N ≤1
00000, M ≤1000000;对于100%的数据, X ≤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
解题思路
因为如果图中有环,环上的点能互相到达,答案一定更优,所以我们先缩点。缩完点之后最优的答案的开始一定在度数为0的点,所以我们思考拓补排序。设Num[x]为x的最大链长,dp[x]为x点处最大链长的总路径数。转移时如果Num[u ]< u点中小点的数量+Num[x],则dp[u]=dp[x],Num[u]=Num[x]+u点中小点的数量。如果Num[u]=Num[x]+u中小点的数量,dp[u]+=dp[x],最后答案取那些Num为mx的dp值之和。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
const int MAXN = 500005;
const int MAXM = 1000005;
inline int rd(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)) {x=(x<<1)+(x<<3)+ch-48;ch=getchar();}
return x*f;
}
int n,m,mod;
int head[MAXN],cnt;
struct Edge{
int nxt,to,fr;
}e[MAXM<<1];
queue<int> q;
inline void add(int bg,int ed){
e[++cnt].to=ed,e[cnt].fr=bg,e[cnt].nxt=head[bg],head[bg]=cnt;
}
int dfn[MAXN],low[MAXN];
int sta[MAXN],top,num,col[MAXN];
int col_num,sum[MAXN],Num[MAXN],dp[MAXN];
bool vis[MAXN];
inline void tarjan(int x){
dfn[x]=low[x]=++num;
sta[++top]=x;
vis[x]=1;
for(register int i=head[x];i;i=e[i].nxt){
int u=e[i].to;
if(!dfn[u]){
tarjan(u);
low[x]=min(low[x],low[u]);
}
else if(vis[u])
low[x]=min(low[x],dfn[u]);
}
if(low[x]==dfn[x]){
int tot=0;
col_num++;
vis[x]=0;
while(sta[top]!=x){
tot++;
vis[sta[top]]=0;
col[sta[top]]=col_num;
top--;
}
top--;tot++;
col[x]=col_num;
sum[col_num]=tot;
}
}
int head_[MAXN],cnt_;
int to_[MAXM],nxt_[MAXM];
int ans,du[MAXN],mx;
int bl[MAXN];
inline void add_(int bg,int ed){
to_[++cnt_]=ed,nxt_[cnt_]=head_[bg],head_[bg]=cnt_;
}
inline void dfs(){
while(q.size()){
int x=q.front();q.pop();
mx=max(mx,Num[x]);
for(register int i=head_[x];i;i=nxt_[i]){
int u=to_[i];du[u]--;
if(!du[u]) q.push(u);
if(bl[u]==x) continue;
if(Num[u]<Num[x]+sum[u]) {
Num[u]=Num[x]+sum[u];
dp[u]=dp[x];
}
else if(Num[u]==Num[x]+sum[u]){
dp[u]+=dp[x];
dp[u]%=mod;
}
bl[u]=x;
}
}
}
inline bool cmp(Edge A,Edge B){
return A.fr<B.fr;
}
int main(){
n=rd();m=rd();mod=rd();
int xx=0,y=0;
for(register int i=1;i<=m;i++){
xx=rd(),y=rd();
add(xx,y);
}
for(register int i=1;i<=n;i++)
if(!dfn[i]) tarjan(i);
for(register int i=1;i<=m;i++)
e[i].fr=col[e[i].fr],e[i].to=col[e[i].to];
for(register int i=1;i<=m;i++)
if(e[i].fr!=e[i].to){
add_(e[i].fr,e[i].to);
du[e[i].to]++;
}
for(register int i=1;i<=col_num;i++)
if(!du[i]) q.push(i),Num[i]=sum[i],dp[i]=1;
dfs();
for(register int i=1;i<=col_num;i++)
if(Num[i]==mx) ans+=dp[i],ans%=mod;
printf("%d\n%d",mx,ans);
}