http://acm.hdu.edu.cn/showproblem.php?pid=3594
明确一点:一个scc并不一定是一个环,也可能是很多环。仙人掌图就是一个例子。
仙人掌图就是 每个环粘结起来,每条边只属于一个环。
有三个性质。
① 没有横向边(具体可以看见百度文库那个讲解,https://wenku.baidu.com/view/ce296043192e45361066f575.html)我理解的就是没有重复边(U,V一样)。
② 没有桥,因为有桥一定构不成SCC
③ u和u的子节点中,low【i】小于dfn[i] 不能大于等于2,存在一个,即意味着存在一个反向边。存在两个边即有两个环。看图
然后讲一下我对tarjan的理解。
low 保存i点经过一定路径能够达到的最小dfn
dfn 保存i点的dfs顺序。
当low 和dfn相等时,即可以证明 i点是该scc dfs序最小的一个,(可以理解为一个scc中的提手。)4也算一个SCC。
所以可以用以下的方法计算。
① 如果SCC的数量大于1,则false
② 如果tarjan找到一个环,那么就记录这个环除了第一个dfn节点之外的所有节点。(因为其他点还会可能在其他环内,并且肯定做第一个点,,因为dfn的顺序。所以这样不会重复加。具体看图,第一个不计算红色的,而蓝的 绿的也只会在开始的时候记录一次。)
但是这样有一个错误的情况,明明不可以,但是输出是可以。。,wa了我好久。。
那就是出现重边的情况。酱紫(貌似一般的tarjan都会删除重边qwq)
解决办法。① 统计的时候,有重边,肯定不能构成仙人掌图。
② 在每个点统计的时候,即使他们没有构成环,如果子节点也在栈里,那么就 dfs统计。。这样肯定会把他们给加重了。就可以了qwq
给出两个解法,我窃以为第二种比较好,
#include <bits/stdc++.h>
/* 仙人掌图有以下性质。
1 没有横向边,也就是用一个延时标记。
或者 直接判断有无 重复边
2 没有桥, 也就是low[v]<=dfn[u],如果存在low[v]>dfn[u]即false
即 连通度为1(整个仙人掌图就是一个scc qwq)
3 每个边只在一个环。1 方法1 统计u的每个子孩子的反向边
直接统计他们的 前驱节点,把这个圈都 给记录一下
但是不要计算 他们这个环中 那个dfn最小的点。
因为如果他们 只是进行环与环普通粘结的话,也会
当前环中的点就会被当做 第一个点。
*/
using namespace std;
const int maxn=2e5+5;
int low[maxn];// 最小能够到达的。
int dfn[maxn];//dfs的顺序
stack<int>s;//栈
bool state[maxn];//标记是否在栈里
int cnt[maxn];// 记录他每个点属于哪个scc
int num[maxn];// 记录他每个scc有多少点
int scc;// 记录scc的数量
int index;//记录dfs的顺序
bool flag;
vector<int>G[maxn];
bool vis[maxn];
int tarjan(int u){
dfn[u]=low[u]=++index;
state[u]=true;
s.push(u);
int sum=0;
for(int i=0;i<G[u].size();i++){
int to=G[u][i];
if(vis[to])
{//printf("first %d\n",to);
flag=false;} //性质1
if(!dfn[to]){
tarjan(to);
if(low[to]>dfn[u])
{ //printf("qiao?? %d %d\n",to,u);
flag=false;}//有桥 性质2
if(low[to]<dfn[u]) sum++;
if(sum==2) flag=false;//性质3
low[u]=min(low[to],low[u]);
}
else if(state[to]){
sum++;
low[u]=min(dfn[to],low[u]);
if(sum==2)
flag=false;
}
//vis[to]=true;
}
if(low[u]==dfn[u]){
scc++;
for(;;){
int x=s.top();s.pop();
state[x]=false;
cnt[x]=scc;
num[scc]++;
if(x==u)break;
}
}
vis[u]=true;
}
void init(){
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
memset(state,false,sizeof(state));
memset(cnt,0,sizeof(cnt));
memset(num,0,sizeof(num));
memset(vis,false,sizeof(vis));
while(!s.empty()){
s.pop();
}
scc=0;
index=0;
for(int i=0;i<maxn;i++)
G[i].clear();
}
int main()
{ int t,m,a,b;
scanf("%d",&t);
while(t--){
init();
scanf("%d",&m);
flag=true;
while(~scanf("%d%d",&a,&b)){
if(!a&&!b) break;
//a++,b++;
G[a].push_back(b);
}
for(int i=0;i<m;i++){
if(!dfn[i])
tarjan(i);
}
if(flag&&scc==1)
puts("YES");
else
puts("NO");
}
return 0;
}
2
#include<cstring>
#include<cstdio>
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e4+10;
const int maxm = 5e4+10;
struct st{
int v,nex;
}edge[maxm];
int hed[maxn],vis[maxn],belon[maxn],in[maxn];
int low[maxn],dfn[maxn],stack1[maxn],fa[maxn];
int n,e,cnt,num,top,ok;
void add(int u,int v){
edge[e].v=v,edge[e].nex=hed[u],hed[u]=e++;
}
void dfs(int u,int v){ //遍历环
while(fa[u]!=v){
in[u]++;
if(in[u]>1){
ok=0;
return ;
}
u=fa[u];
}
}
void targan(int u){ //targan判断是否联通
if(!ok)return ;
low[u]=dfn[u]=++num;
stack1[top++]=u;
vis[u]=1;
for(int i=hed[u];~i;i=edge[i].nex){
int v = edge[i].v;
if(!dfn[v]){
fa[v]=u;
targan(v);
if(low[v]<low[u])low[u]=low[v];
}else if(vis[v]){
low[u]=min(dfn[v],low[u]);
dfs(u,v); //由x沿路径返回并标记一直找到v
if(ok==false)
return ;
}
}
if(dfn[u]==low[u]){
cnt++;
if(cnt>1){
ok=0;
return ;
}
int x ;
do{
x=stack1[--top];
belon[x]=cnt;
vis[x]=0;
}while(x!=u);
}
}
void init(){
memset(dfn,0,sizeof(dfn));
memset(hed,-1,sizeof(hed));
memset(vis,0,sizeof(vis));
memset(in,0,sizeof(in));
e=1;
cnt=top=num=0;
}
int main()
{
int t;scanf("%d",&t);
while(t--){
init();
scanf("%d",&n);
int u,v;
ok=1;
while(scanf("%d%d",&u,&v),u+v){
u++,v++;
//for(int i=hed[u];~i;i=edge[i].nex){
// if(edge[i].v==v)ok=0;
//}
add(u,v);
}
for(int i=1;i<=n&&ok;i++){
if(!dfn[i])targan(i);
}
if(ok)printf("YES\n");
else printf("NO\n");
}
return 0;
}