BZOJ 1093 浅谈tarjan缩点DAG最长路径即拓扑排序

本文介绍了如何利用tarjan算法解决有向图中最长路径问题,重点讨论了半连通分量的概念,以及在处理过程中可能出现的坑点。通过拓扑排序思路,使用BFS更新状态,并通过记录每个点的来源避免重复更新,从而找到缩点后的最长链。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这里写图片描述
(这次的图是自己画的2333)
世界真的很大
这道题有点扯,思路不太好想
关键是想清楚半连通分量的等价类
然后就好办了
有一些坑点
先看一下题吧
description:

 一个有向图G=(V,E)称为半连通的(Semi-Connected),如果满足:?u,v∈V,满足u→v或v→u,即对于图中任意
两点u,v,存在一条u到v的有向路径或者从v到u的有向路径。若G'=(V',E')满足V'?VE'是E中所有跟V'有关的边,
则称G'是G的一个导出子图。若G'G的导出子图,且G'半连通,则称G'G的半连通子图。若G'是G所有半连通子图
中包含节点数最多的,则称G'G的最大半连通子图。给定一个有向图G,请求出G的最大半连通子图拥有的节点数K
,以及不同的最大半连通子图的数目C。由于C可能比较大,仅要求输出CX的余数。

input

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

output

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

首先应该明确半连通分量是个什么东西
有向图,分量,自然先想到强连通分量
如果点u到达点v,点v不到达点u,点v所在的强连通分量u也全部可以到达,而全部不能到达v
如果能的话u和v就在一个强连通分量里了2333
即半连通分量就是强连通分量缩点后的一条链
即求缩点后的图里的最长链
可以看一下这篇
大体来说采用拓扑排序的思路
用bfs的方法更新状态
有个坑点就是两个强连通分量缩点后的点之间有可能不止一条边,可能会导致重复更新
对每一个点记录一下现在的点是由哪一个点更新来的,避免重复
没什么了
完整代码:

#include<stdio.h>
#include<stack>
#include<queue>
#include<cstring>
using namespace std;
struct edge
{
    int last,v,u;
}ed[2000010],ad[2000010];

stack <int> stk;
queue <int> state;

int head[200010],place[200010],vis[200010],ins[200010];
int dfn[200010],low[200010],siz[200010],in[200010];
int dis[200010],nmb[200010];
int num=0,cnt=0,idx=0,mum=0,ans=0,bns=0,n,m,mod,tar;

void add(int u,int v)
{
    num++;
    ed[num].v=v;
    ed[num].u=u;
    ed[num].last=head[u];
    head[u]=num;
}

void ade(int u,int v)
{
    mum++;
    ad[mum].v=v;
    ad[mum].last=head[u];
    head[u]=mum;
}

void tarjan(int u)
{
    dfn[u]=low[u]=++idx;
    vis[u]=ins[u]=1;
    stk.push(u);
    for(int i=head[u];i;i=ed[i].last)
    {
        int v=ed[i].v;
        if(!vis[v])
        {
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(ins[v])
        {
            low[u]=min(low[u],dfn[v]);
        }       
    }
    if(dfn[u]==low[u])
    {
        cnt++;int t=-1;
        while(t!=u)
        {
            t=stk.top();
            place[t]=cnt;
            siz[cnt]++;
            ins[t]=0;
            stk.pop();
        }
    }
}

void rebuild()
{
    memset(head,0,sizeof(head));
    for(int i=1;i<=num;i++)
    if(place[ed[i].u]!=place[ed[i].v])
    {
        ade(place[ed[i].u],place[ed[i].v]);
        in[place[ed[i].v]]++;
    }
}

void top()
{
    memset(vis,0,sizeof(vis));
    for(int i=1;i<=cnt;i++)
        if(in[i]==0)
        {
            state.push(i);
            dis[i]=siz[i];nmb[i]=1;
        }
    while(!state.empty())
    {
        int u=state.front();
        state.pop();
        for(int i=head[u];i;i=ad[i].last)
        {
            int v=ad[i].v;
            in[v]--;
            if(in[v]==0) state.push(v);
            if(vis[v]==u) continue ;
            if(dis[v]<dis[u]+siz[v])
            {
                dis[v]=dis[u]+siz[v];
                nmb[v]=nmb[u];
            }
            else if(dis[v]==dis[u]+siz[v])
                nmb[v]=(nmb[u]+nmb[v])%mod;         
            vis[v]=u;
        }
    }
    for(int i=1;i<=cnt;i++)
    if(dis[i]>ans) ans=dis[i];
    for(int i=1;i<=cnt;i++)
    if(dis[i]==ans)
    bns=(bns+nmb[i])%mod;
}

int main()
{
    scanf("%d%d%d",&n,&m,&mod);
    for(int i=1;i<=m;i++)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        add(u,v);
    }
    for(int i=1;i<=n;i++)
        if(!vis[i]) tarjan(i);
    rebuild();
    top();
    printf("%d\n%d",ans,bns%mod);
    return 0;
}

嗯,就是这样

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值