【USACO 2015 Jan Gold】强连通分量+最长路

NKOJ 3213牧草鉴赏家

问题描述

约翰有n块草场,编号1到n,这些草场由若干条单行道相连。奶牛贝西是美味牧草的鉴赏家,她想到达尽可能多的草场去品尝牧草。

贝西总是从1号草场出发,最后回到1号草场。她想经过尽可能多的草场,贝西在通一个草场只吃一次草,所以一个草场可以经过多次。因为草场是单行道连接,这给贝西的品鉴工作带来了很大的不便,贝西想偷偷逆向行走一次,但最多只能有一次逆行。问,贝西最多能吃到多少个草场的牧草。

输入格式

第一行,两个整数N和M(1<=N,M<=100000)
接下来M行,表示有M条单向道路,每条道路有连个整数X和Y表示,从X出发到达Y。

输出格式

一个整数,表示所求答案


首先可以想到,处于同一个强连通分量的点,如果能够从1号点走到强连通分量中,那么就能够全部吃完。因此我们可以用Tarjan算法缩点。

缩完点之后,就变成了一个有向无环图。然而我们最后还是要回到1号点,所以就有两种情况:

1.只吃完与起点处在同一个强连通分量的所有草;
2.通过逆行回到起点。

主要讨论情况2。此时逆行的边 < a, b > 显然满足从起点能够到达a,且从b能够到达起点。那么为了解决“从b能够到达起点”,对原图建一个反图。分别对原图与反图跑一次最短路算法后,枚举逆边即可。

#include
#include
#include
#define Min(x,y) ((x
#define Max(x,y) ((x>y)?(x):(y))
#define MAXN 100005
#define MAXM 200005
using namespace std;
int N,M,T,Size[MAXN],Ans;

inline int _R()
{
    char s=getchar();int v=0,sign=0;
    while((s!='-')&&(s>57||s<48))s=getchar();
    if(s=='-')sign=1,s=getchar();
    for(;s>47&&s<58;s=getchar())v=v*10+s-48;
    if(sign)v=-v;
    return v;
}

int tot,en[MAXM],nex[MAXM],las[MAXN];
void ADD_scc(int x,int y)
{
    en[++tot]=y;
    nex[tot]=las[x];
    las[x]=tot;
}

stack<int>S;
int VT,scc,be[MAXN],dfn[MAXN],low[MAXN];
bool mark[MAXN];

void Tarjan(int x)
{
    dfn[x]=low[x]=++VT;
    mark[x]=true;
    S.push(x);
    int i,y;

    for(i=las[x];i;i=nex[i])
    {
        y=en[i];
        if(!dfn[y])
        {
            Tarjan(y);
            low[x]=Min(low[x],low[y]);
        }
        else if(mark[y])low[x]=Min(low[x],dfn[y]);
    }
    if(dfn[x]!=low[x])return;
    scc++;
    do
    {
        y=S.top();S.pop();mark[y]=false;
        be[y]=scc;
    }while(y!=x);
}

int Tot,En[MAXM],Las[MAXN],Nex[MAXM],Len[MAXM],St[MAXM];
bool inv[MAXM];
void ADD(int x,int y,int z,int w)
{
    En[++Tot]=y;
    Nex[Tot]=Las[x];
    Las[x]=Tot;
    Len[Tot]=z;
    St[Tot]=x;
    inv[Tot]=w;
}

struct node{int p,al;};

bool in[MAXN][2];
int Dis[MAXN][2];
void SPFA1(int s)
{
    queue<int>Q;
    int i,y,x,z;

    for(i=1;i<=scc;i++)Dis[i][0]=-1e9;
    Dis[s][0]=0;
    Q.push(s);
    while(Q.size())
    {
        x=Q.front();Q.pop();
        in[x][0]=false;
        for(i=Las[x];i;i=Nex[i])
        {
            y=En[i];
            if(inv[i])continue;
            if(Dis[y][0]0]+Len[i])
            {
                Dis[y][0]=Dis[x][0]+Len[i];
                if(!in[y][0]){in[y][0]=true;Q.push(y);}
            }
        }
    }
}
//原图
void SPFA2(int s)
{
    queue<int>Q;
    int i,y,x,z;

    for(i=1;i<=scc;i++)Dis[i][1]=-1e9;
    Dis[s][1]=0;

    Q.push(s);
    while(Q.size())
    {
        x=Q.front();Q.pop();
        in[x][1]=false;
        for(i=Las[x];i;i=Nex[i])
        {
            y=En[i];
            if(!inv[i])continue;
            if(Dis[y][1]1]+Len[i])
            {
                Dis[y][1]=Dis[x][1]+Len[i];
                if(!in[y][1]){in[y][1]=true;Q.push(y);}
            }
        }
    }
}
//反图
int main()
{
    int i,j,x,y;

    scanf("%d%d",&N,&M);
    for(i=1;i<=M;i++)
    {
        scanf("%d%d",&x,&y);
        ADD_scc(x,y);
    }

    for(i=1;i<=N;i++)if(!dfn[i])Tarjan(i);

    for(i=1;i<=N;i++)Size[be[i]]++;


    for(x=1;x<=N;x++)
    {
        for(i=las[x];i;i=nex[i])
        {
            y=en[i];
            if(be[x]==be[y])continue;
            ADD(be[x],be[y],Size[be[y]],0);
            ADD(be[y],be[x],Size[be[x]],1);
        }
    }
    T=be[1];

    SPFA1(T);
    SPFA2(T);
    for(i=1;i<=Tot;i++)
    {
        if(!inv[i])continue;
        x=St[i];y=En[i];
        if(Dis[x][0]!=-1e9&&Dis[y][1]!=-1e9)Ans=Max(Ans,(Dis[x][0]+Dis[y][1]));//枚举逆边,如果Dis为-1e9,说明到不了
    }

    printf("%d",Ans+Size[T]);//与起点在同一强连通分量的草也能吃到
}

你可能感兴趣的:(Tarjan,最短路算法)