NKOJ 3213 牧草鉴赏家(Tarjan缩点+最长路)

P3213【USACO 2015 Jan Gold】牧草鉴赏家

问题描述

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

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

输入格式

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

输出格式

一个整数,表示所求答案

样例输入

7 10
1 2
3 1
2 5
2 4
3 7
3 5
3 6
6 5
7 2
4 7

样例输出

6

首先显然用Tarjan缩点,变成DAG。
由于只用一条反向边,因此我们可以枚举这条反向边,那么路径是1->反向边起点->反向边终点->1
那么显然我们只需要算出1到每一个点经过的最多点数,和每一个点到1经过的最多点数。然后枚举讨论即可。

关于正确性,我们只需要证明除了1号点经过两次外,所有点都只会经过一次即可。那么,首先,我们把这个DAG中的路径分成两种,一种是以1为起点的,一种是以1为终点的,其他的不讨论。那么显然这两种边是不能重合的,而且反向边肯定是从以1为起点的路径连到以1为终点的路径上。故正确性得证。

关于求出上述的最多点数,我们用缩点后的图跑最长路,将终点的缩点前包含点数当成边权即可。正图+反图跑两遍即可。


代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<stack>
#include<queue>
#include<cstring>
#define N 123456
#define M 234567
using namespace std;
inline int _R()
{
    int t=getchar();int o;
    while(t<48||t>57)t=getchar();
    for(o=0;t>47&&t<58;t=getchar())o=o*10+t-48;
    return o;
}
int n,m,VT,scc,size[N],ans=-1e9;
int TOT,LA[N],NE[M],EN[M],ST[M];
int NTOT,NLA[N],NNE[M],NEN[M],NST[M];
int ntot,nla[N],nne[M],nen[M],nst[M];
int dis[N],dist[N],dfn[N],low[N],be[N];
bool mark[N],Mark[N];
stack<int>S;
queue<int>Q;
void ADD(int x,int y)
{
    TOT++;
    ST[TOT]=x;
    EN[TOT]=y;
    NE[TOT]=LA[x];
    LA[x]=TOT;
}
void NADD(int x,int y)
{
    NTOT++;
    NST[NTOT]=x;
    NEN[NTOT]=y;
    NNE[NTOT]=NLA[x];
    NLA[x]=NTOT;
}
void nadd(int x,int y)
{
    ntot++;
    nst[ntot]=x;
    nen[ntot]=y;
    nne[ntot]=nla[x];
    nla[x]=ntot;
}
void Tarjan(int u)
{
    int i,v;
    dfn[u]=low[u]=++VT;
    S.push(u);
    mark[u]=1;
    for(i=LA[u];i;i=NE[i])
    {
        v=EN[i];
        if(!dfn[v])
        {
            Tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(mark[v])low[u]=min(low[u],dfn[v]);
    }
    if(dfn[u]==low[u])
    {
        scc++;
        do{
            v=S.top();
            S.pop();
            mark[v]=0;
            be[v]=scc;
            size[scc]++;
        }while(u!=v);
    }
}
void SPFA(int s)
{
    int i,x,y;
    for(i=1;i<=n;i++)dis[i]=-1e9;
    Mark[s]=1;dis[s]=size[s];Q.push(s);
    while(!Q.empty())
    {
        x=Q.front();
        Q.pop();
        Mark[x]=0;
        for(i=nla[x];i;i=nne[i])
        {
            y=nen[i];
            if(dis[y]<dis[x]+size[y])
            {
                dis[y]=dis[x]+size[y];
                if(!Mark[y])Mark[y]=1,Q.push(y);
            }
        }
    }
}
void spfa(int s)
{
    int i,x,y;
    for(i=1;i<=n;i++)dist[i]=-1e9;
    Mark[s]=1;dist[s]=size[s];Q.push(s);
    while(!Q.empty())
    {
        x=Q.front();
        Q.pop();
        Mark[x]=0;
        for(i=NLA[x];i;i=NNE[i])
        {
            y=NEN[i];
            if(dist[y]<dist[x]+size[y])
            {
                dist[y]=dist[x]+size[y];
                if(!Mark[y])Mark[y]=1,Q.push(y);
            }
        }
    }
}
int main()
{
    int i,j,x,y;
    n=_R();m=_R();
    for(i=1;i<=m;i++)
    {
        x=_R();y=_R();
        ADD(x,y);
    }
    for(i=1;i<=n;i++)if(!dfn[i])Tarjan(i);
    for(i=1;i<=TOT;i++)
    {
        x=ST[i];y=EN[i];
        if(be[x]!=be[y])
        {
            NADD(be[x],be[y]);
            nadd(be[y],be[x]);
        }
    }
    SPFA(be[1]);
    spfa(be[1]);
    for(i=1;i<=ntot;i++)
    {
        x=nst[i];y=nen[i];
        if(dist[x]!=-1e9&&dis[y]!=-1e9)ans=max(ans,dist[x]+dis[y]-size[be[1]]);
    }
    cout<<ans;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值