[ZJOI2007]最大半连通子图 (tarjan缩点+拓扑序dp)

[ZJOI2007]最大半连通子图

链接:https://ac.nowcoder.com/acm/problem/20603
来源:牛客网

题目描述

一个有向图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的余数。

输入描述:

第一行包含两个整数N,M,X。N,M分别表示图G的点数与边数,X的意义如上文所述
接下来M行,每行两个正整数a, b,表示一条有向边(a, b)。
图中的每个点将编号为1,2,3…N,保证输入中同一个(a,b)不会出现两次。
N ≤ 100000, M ≤ 1000000;对于100%的数据, X ≤ 10^8

输出描述:

应包含两行,第一行包含一个整数K。第二行包含整数C Mod X.
示例1

输入

6 6 20070603
1 2
2 1
1 3
2 4
5 6
6 4

输出

3
3

思路:

tarjan缩点得到无环有向图DAG,对缩点后的图每个点的点权就是缩点的大小,答案就是求DAG中最长的链,以及最长的链有几条。拓扑排序后用f[ ]和g[ ]记录答案。

用拓扑序是为了不用考虑重复的情况,然后直接dp即可。

代码:

#include<bits/stdc++.h>
#define sd(x) scanf("%d",&x)
#define lsd(x) scanf("%lld",&x)
#define ms(x,y) memset(x,y,sizeof x)
#define fu(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define all(a) a.begin(),a.end()
#define range(a,x,y) a+x,a+y+x
using namespace std;
using namespace __gnu_cxx;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
typedef pair<int,int> P;
const int N=2e5+99;
ll mod=2147493647;
const int INF=1e9+7;
vector<int> to[N],go[N];
int dfn[N],low[N],cnt,st[N],tt,point,col[N],num[N],du[N];
bool vis[N];
int n,m;
ll f[N],g[N];
map<int,int> used[N];
void tarjan(int u)
{
    vis[u]=1;
    dfn[u]=low[u]=++cnt;
    st[++tt]=u;
    for(int v:to[u])
    {
        if(!dfn[v])
        {
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(vis[v]) low[u]=min(low[u],dfn[v]);
    }
    if(dfn[u]==low[u])
    {
        point++;
        do
        {
            col[st[tt]]=point;
            num[point]++;
            vis[st[tt]]=0;
        } while (st[tt--]!=u);
    }
}
void topusort()
{
    queue<int> q;
    fu(i,1,point)
    {
        if(du[i]==0) q.push(i);
        g[i]=1;
        f[i]=num[i];
    }
    while(!q.empty())
    {
        int x=q.front();q.pop();
        for(int y:go[x])
        {
            //记录答案
            if(f[y]<f[x]+num[y])
            {
                f[y]=f[x]+num[y];
                g[y]=g[x];//因为只能从x到y
            }
            else if(f[y]==f[x]+num[y])
            {
                g[y]+=g[x];
                g[y]%=mod;
            }
            //处理度数
            du[y]--;
            if(!du[y]) q.push(y);
        }
    }
}
int main() 
{
    cin>>n>>m>>mod;
    fu(i,1,m)
    {
        int u,v;
        cin>>u>>v;
        to[u].push_back(v);
    }
    fu(i,1,n)
    {
        if(dfn[i]==0) tarjan(i);
    }
    //按颜色建图
    fu(i,1,n)
    {
        for(int j:to[i])
        {
            int u=col[i],v=col[j];
            if(u==v||used[u][v]) continue;
            used[u][v]=1;
            go[u].push_back(v);
            du[v]++;
        }
    }
    topusort();
    ll mx=0,ans=0;//记录最长的链和方法数
    fu(i,1,point)
    {
        mx=max(mx,f[i]);
    }
    fu(i,1,point)
    {
        if(mx==f[i]) ans+=g[i],ans%=mod;
    }
    printf("%lld\n",mx);
    printf("%lld\n",ans);
    return 0;
}

 

posted on 2020-12-28 11:10  Aminers  阅读(64)  评论(0编辑  收藏  举报

导航