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
#include
#include
#include
#include

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]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.frint 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);
}

你可能感兴趣的:(图论--tarjan强连通分量)