P3119 [USACO15JAN]草鉴定Grass Cownoisseur

传送门

显然每次走到一个联通块肯定要把整个联通块的草场都走一遍,考虑缩点

然后直接建分层图跑最长路就好了

(为了方便,以下的强连通分量均称为点)

但是有一个小问题,如果反着走可能走到以前走过的点,怎么判断(因为每个点只有一次贡献)

其实根本不用判断,因为如果从一号点出发,走出去后要走回来一定要逆行一次(因为没有环)

如果你把逆行机会用在走以前走过的点(出发点除外),那你就永远走不回出发点了

所以不会对答案产生贡献,然后就是Dijk跑最长路了

分层图十分显然,dis[ i ] [ 0/1 ] 表示到达点 i ,是否用过逆行机会时的最长路

建图时记得建一下反图就好了

// luogu-judger-enable-o2
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<iostream>
#include<vector>
#include<queue>
using namespace std;
typedef long long ll;
inline int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
    return x*f;
}
const int N=2e5+7;
int n,m;
int fir[N],from[N<<1],to[N<<1],cntt=0;
inline void add(int &a,int &b)
{
    from[++cntt]=fir[a];
    fir[a]=cntt; to[cntt]=b;
}
//以下tarjan模板
int dfn[N],low[N],dfs_clock,st[N],Top,be[N],tot,cnt[N];
void Tarjan(int x)
{
    dfn[x]=low[x]=++dfs_clock; st[++Top]=x;
    for(int i=fir[x];i;i=from[i])
    {
        int &v=to[i];
        if(!dfn[v]) Tarjan(v),low[x]=min(low[x],low[v]);
        else if(!be[v]) low[x]=min(low[x],dfn[v]);
    }
    if(low[x]==dfn[x])
    {
        tot++; cnt[tot]=1;
        while(st[Top]!=x)
        {
            cnt[tot]++;
            be[st[Top--]]=tot;
        }
        be[st[Top--]]=tot;
    }
}
vector <int> v1[N],v2[N];//为了方便直接用vector存图,v1是正图,v2是反图
void build()//建图
{
    for(int i=1;i<=n;i++)
        for(int j=fir[i];j;j=from[j])
        {
            int &v=to[j]; if(be[i]==be[v]) continue;
            v1[be[i]].push_back(be[v]); v2[be[v]].push_back(be[i]);
        }
}
int dis[N][2];
struct data
{
    int pos,dis,p;//当前位置,经过距离,是否逆行过
    inline bool operator < (const data &tmp) const {
        return dis<tmp.dis;
    }
};
priority_queue <data> q;
void Dijk()
{
    memset(dis,128,sizeof(dis)); q.push((data){be[1],0,0}); dis[be[1]][0]=0;//初始状态
    data x;
    while(!q.empty())
    {
        x=q.top(); q.pop(); if(dis[x.pos][x.p]!=x.dis) continue;
        int len=v1[x.pos].size();
        for(int i=0;i<len;i++)
        {
            int &v=v1[x.pos][i];
            if(dis[v][x.p]<x.dis+cnt[v])
                dis[v][x.p]=x.dis+cnt[v],q.push((data){v,dis[v][x.p],x.p});
        }
        if(!x.p)//如果没逆行过就考虑逆行
        {
            len=v2[x.pos].size();
            for(int i=0;i<len;i++)
            {
                int &v=v2[x.pos][i];
                if(dis[v][1]<x.dis+cnt[v])
                    dis[v][1]=x.dis+cnt[v],q.push((data){v,dis[v][1],1});
            }
        }
    }
}
int main()
{
    int a,b;
    n=read(); m=read();
    for(int i=1;i<=m;i++)
    {
        a=read(); b=read();
        add(a,b);
    }
    for(int i=1;i<=n;i++)
         if(!dfn[i])    Tarjan(i);//记得每个点都要缩起来,正图走不通可能反图能走
    if(tot==1) { printf("%d",n); return 0; }//记得特判一波
    build();
    Dijk();
    printf("%d",max(dis[be[1]][0],dis[be[1]][1]));
    return 0;
}
原文地址:https://www.cnblogs.com/LLTYYC/p/9900015.html