Luogu P3119 [USACO15JAN] Grass Cownoisseur G 题解 [ 蓝 ] [ Tarjan ] [ 分层图 ] [ 枚举 ]

Grass Cownoisseur:还不错的一道 tarjan 题。

观察

首先不难发现同一个 SCC 里的点互相可达,能去其中一个就可以去其他所有点,于是先跑 tarjan 缩点。

然后这张图就成了一个 DAG,要求你最多反转一条边,最大化只经过这一条边一次的一个包含点 \(1\) 所在 SCC 的环的权值。

考虑翻转一条边代表什么,显然翻转一条边就是要走到一些本来去不了的点那里,然后再返回 \(1\)。整体的形态就是从 \(1\) 走到 \(u\),然后走反边,再从 \(v\) 走到 \(1\)

同时还有一个结论,就是在走过翻转后的边之后,就不能再回到原来走过的点去了。原因很简单,如果还要走回去的话,还不如不翻转这条边呢。并且,如果走到原来的点,就一定没办法再返回 \(1\),因为 DAG 有特殊性质,\(1\) 原来能到的点是一定不能在原图上返回 \(1\) 的。

简而言之就是所有点可以被分为 \(3\) 类,一个是能被 \(1\) 到达的,一个是能到达 \(1\) 的,还有是孤立点的。

枚举

到这里就是好做的了,我们先正向拓扑一遍,求出从 \(1\) 到第一类点的最长链。在反向拓扑一遍,求出第二类点到 \(1\) 的最长链。然后枚举每条边,如果边的两端分别是一类和二类点,就说明可以走这条边的反向边,更新答案即可。

注意可以有不走任何反向边的情况。

时间复杂度 \(O(n+m)\)

我写的是这种做法。

分层图

分层图也是一样的,把所有点拆成两个点,然后反向边连接两层即可。因为“如果走到原来的点,就一定没办法再返回 \(1\) 了”这个优良性质,所以一定不会让一个点被重复贡献,跑拓扑最长路即可。

时间复杂度 \(O(n+m)\)

代码

#include <bits/stdc++.h>
#define fi first
#define se second
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
#define lc(x) (tr[x].ls)
#define rc(x) (tr[x].rs)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
using pi=pair<int,int>;
const int N=100005,M=1000005;
int n,m,ans;
int h[N],idx=0;
struct Edge{
    int v,ne;
}e[M];
void add(int u,int v)
{
    e[++idx]={v,h[u]};
    h[u]=idx;
}
int dfn[N],low[N],stk[N],tp,scc[N],sz[N],id,tot,dp[2][N];
bitset<N>instk;
void tarjan(int u)
{
    dfn[u]=low[u]=++tot;
    stk[++tp]=u;instk[u]=1;
    for(int i=h[u];i;i=e[i].ne)
    {
        int v=e[i].v;
        if(dfn[v]==0)
        {
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(instk[v])
        {
            low[u]=min(low[u],dfn[v]);
        }
    }
    if(dfn[u]==low[u])
    {
        id++;
        int x;
        do{
            x=stk[tp--];
            instk[x]=0;
            scc[x]=id;
            sz[id]++;
        }while(x!=u);
    }
}
vector<int>g[2][N];
int main()
{
    //freopen("sample.in","r",stdin);
    //freopen("sample.out","w",stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin>>n>>m;
    while(m--)
    {
        int u,v;
        cin>>u>>v;
        add(u,v);
    }
    for(int i=1;i<=n;i++)if(dfn[i]==0)tarjan(i);
    for(int u=1;u<=n;u++)
    {
        int sccu=scc[u];
        for(int i=h[u];i;i=e[i].ne)
        {
            int v=e[i].v;
            int sccv=scc[v];
            if(sccu!=sccv)
            {
                g[0][sccu].push_back(sccv);
                g[1][sccv].push_back(sccu);
            }
        }
    }
    memset(dp,-0x3f,sizeof(dp));
    dp[0][scc[1]]=sz[scc[1]];
    for(int u=id;u>=1;u--)
    {
        for(auto v:g[0][u])
        {
            dp[0][v]=max(dp[0][v],dp[0][u]+sz[v]);
        }
    }
    dp[1][scc[1]]=0;
    for(int u=1;u<=id;u++)
    {
        for(auto v:g[1][u])
        {
            dp[1][v]=max(dp[1][v],dp[1][u]+sz[v]);
        }
    }
    ans=max(ans,sz[scc[1]]);
    for(int u=1;u<=id;u++)
    {
        for(auto v:g[0][u])
        {
            ans=max(ans,dp[0][v]+dp[1][u]);
        }
    }
    cout<<ans;
    return 0;
}
posted @ 2025-04-19 15:51  KS_Fszha  阅读(15)  评论(0)    收藏  举报