BZOJ 1093 ZJOI 2007 最大半连通子图 强联通分量+拓扑图DP

Tarjan 专栏收录该内容
2 篇文章 0 订阅

今天是放假的第一天(不说什么废话了)
什么是半连通子图?就是此图中包含的所有点两两点之间至少有一条单向路径。
题目问了两个问题
1.最大半连通子图的大小
2.最大半连通子图的个数

好了,这个问题看上去确实恶心,但不难发现,一个强连通子图一定是半连通的。
而且任何点和强连通子图中的任意一个点有连接,那么它就和所有强连通子图中的点有半连通关系。
那么这真是极好的,tarjan缩点,一切都ok了。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<vector>
#include<algorithm>
#include<stack>
using namespace std;
typedef long long ll;
inline ll read()
{
    char ls=getchar(),k=0;for(;ls<'0'||ls>'9';k=ls,ls=getchar());
    ll x=0;for(;ls>='0'&&ls<='9';ls=getchar())x=x*10+ls-'0';
    if(k=='-')x=-x;return x;
}

stack<ll>zhan;
ll edge[1000005];
ll z[1000005];

ll last[100005];
ll low[100005];
ll dfn[100005];
ll pzhan[100005];
ll visit[100005];

ll mod;
ll n,m;
ll s;
////////////////////////
ll n2;
ll ji;
ll goal[100005];
ll dj[100005];
ll zhi[100005];
ll back[100005];
///////////////////////
ll ji2;
ll edge2[1000005];
ll z2[1000005];
ll last2[100005];
ll dq[100005];
ll rd[100005];
ll kp[100005];
ll sign;
////////////////////////
ll edge3[1000005];
ll z3[1000005];
ll last3[100005];
/////////////////////
ll maxn;
ll sum;
ll zl[100005];
ll maxx;
ll summ;
ll pd[100005];

inline void tarjan(ll d)//强连通分量 
{
    dfn[d]=low[d]=++s;
    zhan.push(d);pzhan[d]=1;visit[d]=1;
    for(ll k=last[d];k!=0;k=z[k])
    {
        ll v=edge[k];
        if(visit[v]==0)
        {
            tarjan(v);
            low[d]=min(low[d],low[v]);
        }
        else if(pzhan[v]==1)
        {
            low[d]=min(low[d],dfn[v]);
        }
    }
    if(dfn[d]==low[d])//规划旧点集和新点集 
    {
        ++n2;
        ll g=zhan.top();
        while(dfn[g]!=low[g])
        {
            ++dq[n2];
            goal[g]=n2;
            dj[++ji]=g;
            zhi[ji]=back[n2];
            back[n2]=ji;
            pzhan[g]=0;
            zhan.pop();
            g=zhan.top();
        }
        ++dq[n2];
        goal[g]=n2;
        dj[++ji]=g;
        zhi[ji]=back[n2];
        back[n2]=ji;
        pzhan[g]=0;zhan.pop();
    }
    return;
}

void kpsort()//拓扑排序 
{
    for(int i=1;i<=n2;++i)
    {
        ll d=kp[i];
        for(int k=last3[d];k!=0;k=z3[k])
        {
            ll v=edge3[k];
            --rd[v];
            if(rd[v]==0)
            kp[++sign]=v;
        }
    }
    return;
}

void dp()//拓扑图动态规划 
{
    for(int i=1;i<=n2;++i)
    {
        ll d=kp[i];
        maxx=0;
        summ=1;
        for(int k=last2[d];k!=0;k=z2[k])
        {
            ll v=edge2[k];
            if(dq[v]==maxx)
            {
                summ+=zl[v];
                summ=summ%mod;
            }
            else if(dq[v]>maxx)
            {
                maxx=dq[v];
                summ=zl[v];
            }
        }
        dq[d]+=maxx;
        zl[d]=summ;
        if(dq[d]==maxn)
        {
            sum+=zl[d];
            sum=sum%mod;
        }
        else if(dq[d]>maxn)
        {
            maxn=dq[d];
            sum=zl[d];
        }
    }
    return;
}

int main()
{
    n=read(),m=read(),mod=read();
    for(int i=1;i<=m;++i)
    {
        ll a1=read(),b1=read();
        edge[i]=b1;
        z[i]=last[a1];
        last[a1]=i;
    }

    for(int i=1;i<=n;++i)
    {
        if(visit[i]==0)
        tarjan(i);
    }

    for(int i=1;i<=n2;++i)//建立新图的边 
    {
        for(int k=back[i];k!=0;k=zhi[k])
        {
            ll v=dj[k];
            for(int kk=last[v];kk!=0;kk=z[kk])
            {
                ll u=edge[kk];
                ll gs=goal[u];
                if(gs!=i&&pd[gs]!=i)
                {
                    pd[gs]=i;
                    ++rd[gs];
                    edge2[++ji2]=i;//统计一个点的出边(拓扑排序用) 
                    z2[ji2]=last2[gs];
                    last2[gs]=ji2;                  
                    edge3[ji2]=gs;//统计一个点的入边(动态规划用) 
                    z3[ji2]=last3[i];
                    last3[i]=ji2;
                }
            }
        }
    }

    for(int i=1;i<=n2;++i)//将入度为0的点进入拓扑序列 
    {
        if(rd[i]==0)
        {
            zl[i]=1;
            kp[++sign]=i;
        }
    }
    kpsort();//拓扑排序 
    dp();//动态规划 
    cout<<maxn<<endl<<sum<<endl;
    return 0;
}
  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值