P2272 [ZJOI2007]最大半连通子图

Posted lck-lck

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了P2272 [ZJOI2007]最大半连通子图相关的知识,希望对你有一定的参考价值。

传送门

题目简单来说就是给一个有向图,将图转化为DAG图后,求图中最长链及最长链的个数。

思路

  用 tarjan 缩点重构将原图转换为一个有向无环图,让后在新图上跑 topo 求出最长链。

  最长链的个数可以用动态规划,设 e[ i ] 表示新图中以 i 为终点的方案数,那么 e[ i ] 就等于连到 i 且满足距离等于起点到 i 的临时最长距离的点的 e 之和。

  最后查找距离等于最长链的点,答案就是它们的方案数之和。

标程

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<string>
#include<cstdlib>
#include<stack>
#include<vector>
#include<queue>
#include<deque>
#include<map>
#include<set>
using namespace std;
#define maxn 1000001 
int n,m,mod;
int x[maxn],y[maxn];//存边 
int cnt=0;
int de[maxn],to[maxn],head[maxn],nex[maxn];//链式前向星 
int ue[maxn];
int t=0,w=0;
int ans=0;
int e[maxn];
int dis[maxn];//topo
int num=0,top=0,col=0;
int dfn[maxn],low[maxn],st[maxn],co[maxn],ins[maxn];//tarjan
int nu[maxn];//重新记录边(去重) 
inline  int read()
{
    int kr=1,xs=0;
    char ls;
    ls=getchar();
    while(!isdigit(ls))
    {
        if(!(ls^45))
            kr=-1;
        ls=getchar();
    }
    while(isdigit(ls))
    {
        xs=(xs<<1)+(xs<<3)+(ls^48);
        ls=getchar();
    } 
    return xs*kr;
}
inline void add(int x,int y)
{
    nex[++cnt]=head[x];
    head[x]=cnt;
    to[cnt]=y;
}
inline void tarjan(int u)//tarjan缩点 
{
    dfn[u]=low[u]=++num;
    st[++top]=u;
    for(int i=head[u];i;i=nex[i])
    {
        int v=to[i];
        if(!dfn[v])
        {
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(!co[v])
            low[u]=min(low[u],dfn[v]);
    }
    if(low[u]==dfn[u])
    {
        co[u]=++col;
        ++ins[col];//ins为强连通分量的大小 
        while(st[top]!=u)
        {
            ++ins[col];
            co[st[top]]=col;
            --top;
        }
        --top; 
    }
}
inline bool cmp(int a,int b)
{
    if(x[a]!=x[b]) return x[a]<x[b];
    return y[a]<y[b];
}
inline void remove()//去除重边,否则会影响方案数的统计 
{
    for(int i=1;i<=m;i++)
    {
        nu[i]=i;
        x[i]=co[x[i]];
        y[i]=co[y[i]];
    }
    sort(nu+1,nu+m+1,cmp);
}

inline void build_map()
{
    remove(); //去除重边 
    cnt=0;
    memset(head,0,sizeof(head));
    for(int i=1;i<=m;i++)
    {
        int z=nu[i];
        if((x[z]!=y[z])&&(x[z]!=x[nu[i-1]]||y[z]!=y[nu[i-1]]))
        {
            ++de[y[z]];
            add(x[z],y[z]);
        }
    }
}//重构图 
inline void topo()
{
    for(int i=1;i<=col;i++)
    {
        if(!de[i])
        {
            ue[++w]=i;
            dis[i]=ins[i];
            e[i]=1;
            if(dis[ans]<dis[i]) ans=i;
        } 
    }//topo排序初始入队 
    while(t<w)
    {
        int u=ue[++t];
        for(int i=head[u];i;i=nex[i])
        {
            int v=to[i];
            --de[v];
            if(dis[v]<dis[u]+ins[v])//临时最长距离被更新,重新统计方案数 
            {
                dis[v]=dis[u]+ins[v];
                e[v]=0;
                if(dis[ans]<dis[v]) ans=v;
            } 
            if(dis[v]==dis[u]+ins[v])//满足距离条件,累加方案数 
            {
                e[v]=(e[v]+e[u])%mod;
            }
            if(!de[v]) ue[++w]=v;
        }
    }//topo排序 
}
int main()
{
    n=read();m=read();mod=read();
    for(int i=1;i<=m;i++)
    {
        x[i]=read();y[i]=read();
        add(x[i],y[i]);
    }
    for(int i=1;i<=n;i++)
        if(!dfn[i])
            tarjan(i);
    build_map();//重构图 
    topo();//topo寻找最长链和最长链个数 
    int tot=0;
    for(int i=1;i<=n;i++)
        if(dis[i]==dis[ans])//统计答案 
            tot=(tot+e[i])%mod; 
    printf("%d
%d
",dis[ans],tot);//输出最长距离、方案数 
return 0;
}

 

以上是关于P2272 [ZJOI2007]最大半连通子图的主要内容,如果未能解决你的问题,请参考以下文章

bzoj 1093: [ZJOI2007]最大半连通子图

[BZOJ1093][ZJOI2007]最大半连通子图

[ZJOI2007]最大半连通子图

ZJOI2007最大半联通子图

bzoj 1093: [ZJOI2007]最大半连通子图

tarjan 拓扑排序 dpbzoj1093: [ZJOI2007]最大半连通子图