Tarjan(强连通分量缩点) - ZJOI 2007 最大半连通子图 - 洛谷 P2272

Tarjan - ZJOI 2007 最大半连通子图 - 洛谷 P2272

一个有向图 G=(V,E) 称为半连通的 (Semi-Connected),如果满足:∀u,v∈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 的余数。

输入格式

第一行包含三个整数 N,M,X。N,M 分别表示图 G 的点数与边数,X 的意义如上文所述;

接下来 M 行,每行两个正整数 a,b,表示一条有向边 (a,b)。

图中的每个点将编号为 1 到 N,保证输入中同一个 (a,b) 不会出现两次。

输出格式

应包含两行。

第一行包含一个整数 K,第二行包含整数 C mod X。

数据范围

1 ≤ N ≤ 1 0 5 , 1 ≤ M ≤ 1 0 6 , 1 ≤ X ≤ 1 0 8 1≤N≤10^5, 1≤M≤10^6, 1≤X≤10^8 1N105,1M106,1X108

输入样例:

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

输出样例:

3
3

分析:

由 题 意 , 强 连 通 图 也 是 半 连 通 图 , 且 强 连 通 图 中 点 的 数 量 一 定 大 于 半 连 通 图 。 由题意,强连通图也是半连通图,且强连通图中点的数量一定大于半连通图。

最 大 半 连 通 子 图 等 价 于 求 一 个 强 连 通 分 量 构 成 的 最 长 链 。 最大半连通子图等价于求一个强连通分量构成的最长链。

我 们 首 先 通 过 t a r j a n 算 法 进 行 缩 点 , 重 新 建 立 拓 扑 图 。 我们首先通过tarjan算法进行缩点,重新建立拓扑图。 tarjan

接 着 我 们 在 拓 扑 图 上 进 行 d p 。 接着我们在拓扑图上进行dp。 dp

f [ i ] : 已 第 i 个 强 连 通 分 量 为 终 点 的 路 径 中 , 所 有 强 连 通 分 量 中 , 点 的 数 量 之 和 的 最 大 值 。 f[i]:已第i个强连通分量为终点的路径中,所有强连通分量中,点的数量之和的最大值。 f[i]i

g [ i ] : 点 的 数 量 和 的 最 大 值 为 f [ i ] 的 不 同 方 案 总 数 。 g[i]:点的数量和的最大值为f[i]的不同方案总数。 g[i]f[i]

注意:

本 题 强 连 通 分 量 之 间 会 有 重 边 , 为 了 避 免 遍 历 时 重 复 计 算 , 我 们 要 对 各 连 通 分 量 之 间 的 边 去 重 。 本题强连通分量之间会有重边,为了避免遍历时重复计算,我们要对各连通分量之间的边去重。

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<unordered_set>

#define ll long long

using namespace std;

const int N=100010, M=2000010;

int n,m,mod;
int h[N],hs[N],e[M],ne[M],idx;
int stk[N],top;
bool in_stk[N];
int dfn[N],low[N],timestamp;
int id[N],scc_cnt,Size[N];
int f[N],g[N];

void add(int h[],int a,int b)
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}

void tarjan(int u)
{
    dfn[u]=low[u]=++timestamp;
    stk[++top]=u,in_stk[u]=true;
    for(int i=h[u];~i;i=ne[i])
    {
        int j=e[i];
        if(!dfn[j])
        {
            tarjan(j);
            low[u]=min(low[u],low[j]);
        }
        else if(in_stk[j]) low[u]=min(low[u],dfn[j]);
    }
    
    if(dfn[u]==low[u])
    {
        ++scc_cnt;
        int y;
        do
        {
            y=stk[top--];
            in_stk[y]=false;
            id[y]=scc_cnt;
            Size[scc_cnt]++;
        }while(y!=u);
    }
}

void dp()
{
    for(int i=scc_cnt;i;i--)    //tarjan算法过后 按照编号递减 已是拓扑序
    {
        if(!f[i])
        {
            f[i]=Size[i];
            g[i]=1;
        }

        for(int j=hs[i];~j;j=ne[j])
        {
            int k=e[j];
            if(f[k]<f[i]+Size[k])
            {
                f[k]=f[i]+Size[k];
                g[k]=g[i];
            }
            else if(f[k]==f[i]+Size[k])
            	g[k]=(g[k]+g[i])%mod;
        }
    }
}

int main()
{
    scanf("%d%d%d",&n,&m,&mod);
    
    memset(h,-1,sizeof h);
    memset(hs,-1,sizeof h);
    int a,b;
    for(int i=0;i<m;i++)
    {
        scanf("%d%d",&a,&b);
        add(h,a,b);
    }
    
    for(int i=1;i<=n;i++)
        if(!dfn[i])
            tarjan(i);
            
    unordered_set<ll> S;    //哈希函数 u*100000+v ,因为点的编号在1e5以内
    for(int i=1;i<=n;i++)   //缩点后重新建图
        for(int j=h[i];~j;j=ne[j])
        {
            int k=e[j];
            int u=id[i],v=id[k];
            ll h=u*100000ll+v;
            if(u!=v && !S.count(h)) 
            {
                add(hs,u,v);
                S.insert(h);
            }
        }
        
    dp();
    
    int maxf=0,sum=0;
    for(int i=1;i<=scc_cnt;i++)
        if(maxf<f[i])
        {
            maxf=f[i];
            sum=g[i];
        }
        else if(maxf==f[i]) sum=(sum+g[i])%mod;
        
    printf("%d\n%d\n",maxf,sum);
    
    return 0;
    
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值