P3213【USACO 2015 Jan Gold】牧草鉴赏家
时间限制 : 10000 MS 空间限制 : 65536 KB
问题描述
约翰有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
提示
贝西的行走线路是1, 2, 4, 7, 2, 5, 3, 1 ,在5到3的时候逆行了一次。
来源 翻译 By BossHe,感谢Wo_ai_WangYuan加上数据范围
为什么牛行道是单向的
题解
简单分析解决条件
这道题目给我们了一个非常重要的条件就是 要回到起点
再加上题目中的边都是有向边
所以解法非常简单 就是 找环
先不考虑那一条可以逆行的边(为什么没有交警奶牛它还要偷偷逆行)
那么这样的话解法就非常简单了 Tarjan找环,然后一号点(起点)所在的环的点的个数
原因
以1号点为起点 同时以一号点为终点 只能是绕了一圈以后又回到这个点
而Tarjan找环的时候,是会把多个环套在一起的情况看做一个环
这样子一号点所在的环即使包括其它子环的话,那也是能够被计算进去的
再把那一条可逆的边给加进去
那么这个时候题目的另外一个条件就显得很重要了
每个点可以经过多次(但是不再计算个数)
那么这个时候我们就可以采取缩点来降低难度了
举例如下
易得 最优的路径为 S->2->3-(反向到达)->4->5->6->S
但是这个样子很难处理环的逆向边(起码很难想)
所以我们选择Tarjan找环
如图得到
一共找到两个环 然后把这两个环所包含的点缩到一个点当中(如果没有环的话 一个点就是一个环)
此时 走过第一个点相当于走过原来的1、2、3号点
走过第二个点相当于走过原来的4、5、6号点
因此就有了一个全新的没有环的图
那么我们要做的就是再一次找环
或者更直接的说 造环
如何理解呢?
因为即使缩了点之后你还是要从原来的起点所在的点出发再回到这个点
但是现在的图当中已经没有环了 所以我们就需要通过改一条逆向边来构成环
具体的做法是什么呢?
对于一个只差一条边就能够构成环的半成品环
一定有且仅有一条边能够在修改之后构成环
也就是说 从起点S出发 能够到达这条边的某一个点
而从这条边的另一个点出发一定能够到达终点
所以最直白的做法就是枚举边
设边的起点为s,终点为e,边的方向由s->e
那么如果有 源点S能够到达e 而s也能够到达源点S
这条边修改之后就一定能够构成环
但是这样的做法岂不是要跑很多遍的图
所以
优化就来了
对于源点S能够到达的点p 在跑了一遍以S为源点的图之后 dis[p]!=inf(无限大)
那么问题就是如何快确定一个点能否从它出发回到源点呢?
反向建图
因为所有的能够到达源点的点都是正向到达源点的
所以源点也能够逆向到达他们
所以我们就把所有的边都逆向一次
这样源点就可以正着走到它们了
然后按照上面说的随便枚举一下边就好讨论了
附上对拍代码
#include <iostream>
#include <cstdio>
#include <stack>
#include <queue>
using namespace std;
inline int input()
{
char c=getchar();int o;
while(c>57||c<48)c=getchar();
for(o=0;c>47&&c<58;c=getchar())o=(o<<1)+(o<<3)+c-48;
return o;
}
int dism=0,n,m;
int all=0,star[101234],STar[101234],Star[101234],nxt[101234],NXt[101234],Nxt[101234],ent[101234],ENt[101234],Ent[101234];
int belong[101234],have[101234],disA[101234],disB[101234];
stack<int>T;
int VT=0,cnt=0,pos[101234],low[101234];
bool In[101234];
int ADD(int s,int e)
{
nxt[++all]=star[s];
star[s]=all;
ent[all]=e;
}
void add(int s,int e)
{
NXt[++all]=STar[s];
Nxt[all]=Star[e];
STar[s]=Star[e]=all;
ENt[all]=e;
Ent[all]=s;
}
void Tarjan(int x)//找环
{
pos[x]=low[x]=++VT;
T.push(x);In[x]=1;
for(int bian=star[x],e=ent[bian];bian;bian=nxt[bian],e=ent[bian])
{
if(!pos[e])
{
Tarjan(e);
low[x]=min(low[x],low[e]);
}
else if(In[e])low[x]=min(low[x],pos[e]);
}
if(pos[x]==low[x])
{
cnt++;int p;
do
{
p=T.top();T.pop();In[p]=0;
belong[p]=cnt;
have[cnt]++;
}while(p!=x);
}
}
struct grass
{
int p,dis,z,fa;
bool operator <(const grass &b)const
{
return dis<b.dis;
}
}a,ad;
priority_queue<grass>go;
void Dij()
{
a.p=belong[1];disA[belong[1]]=a.dis=have[belong[1]];
go.push(a);
int s;bool k;
while(go.size())//跑正向图
{
a=go.top();go.pop();
s=a.p;
for(int bian=STar[s],e=ENt[bian];bian;bian=NXt[bian],e=ENt[bian])
if(disA[e]<disA[s]+have[e])
{
disA[e]=disA[s]+have[e];
ad.p=e;ad.dis=disA[e];
go.push(ad);
}
}
a.p=belong[1];disB[belong[1]]=a.dis=have[belong[1]];
go.push(a);
while(go.size())//跑逆向图
{
a=go.top();go.pop();
s=a.p;
for(int bian=Star[s],e=Ent[bian];bian;bian=Nxt[bian],e=Ent[bian])
if(disB[e]<disB[s]+have[e])
{
disB[e]=disB[s]+have[e];
ad.p=e;ad.dis=disB[e];
go.push(ad);
}
}
for(int s=1;s<=cnt;s++)//枚举边讨论
for(int bian=STar[s],e=ENt[bian];bian;bian=NXt[bian],e=ENt[bian])
if(disA[e]&&disB[s])
dism=max(dism,disA[e]+disB[s]-have[belong[1]]);//更新结果
}
int main()
{
freopen("taste.in","r",stdin);
freopen("taste.out","w",stdout);
int s,e;
n=input();m=input();
for(int i=1;i<=m;i++)
{
s=input();e=input();
ADD(s,e);
}
for(int i=1;i<=n;i++)if(!pos[i])Tarjan(i);
all=0;
for(int i=1;i<=n;i++)
{
s=belong[i];
for(int bian=star[i],e=belong[ent[bian]];bian;bian=nxt[bian],e=belong[ent[bian]])
if(s!=e)add(s,e);//缩点
}
Dij();
printf("%d",max(dism,have[belong[1]]));
}