这题搞了好久。。
首先一条双向边(u,v)可以变成单向边的条件是存在一个经过这条边的环,只需要按这个环的方向把这个环上的所有双向边变成单向就可以了,如果这个环上都是双向边,就随便定一个方向就可以了。
所以我们考虑将双向边拆成两条单向边dfs,这样树边至少存在一个向下的方向,但也有可能是向下的单向边。
但是考虑非树边的时候首先注意到一件事情,就是两条非树边之间可能互相影响,就是说一条非树边可能不能与树边构成环,但是可能与另几条非树边构成环。但是,其实这种影响必然是按dfs序从先到后的。
对于一条非树边(u,v),如果(lca,v)的树边中不存在单向边,那么我们就认为这条非树边与从u到v的树边构成了环,并且把(u,lca)的单向边清为双向边。(其实就是把环缩点。。)
这样的话如果(u,v)并不能构成环,那么它在dfs的过程中之后也不会构成环了,因为(lca,v)已经被dfs过了,只有在lca在v一侧的子树中的出边才能消除其中的单向边,而它们已经被dfs过了。(所以说是没有后效性的!)
所以我们可以在dfs过程中顺便tarjan lca,然后再用一个并查集维护一条边上面第一条单向边即可。
#include<cstdio>
#include<iostream>
using namespace std;
#include<algorithm>
#include<cstring>
const int N=1e5+5,M=2e5+5;
char * cp=(char *)malloc(4000000);
inline void in(int &x)
{
while(*cp<'0'||*cp>'9')++cp;
for(x=0;*cp>='0'&&*cp<='9';)x=x*10+(*cp++^'0');
}
int next[M*2],succ[M*2],ptr[N],etot=1;
int other[M*2];
inline void addedge(int u,int v)
{
next[etot]=ptr[u],ptr[u]=etot,succ[etot++]=v;
}
int ans;
int delta[N];
int depth[N];
bool vst[N];
int ftr[N];
int fa[N],ff[N];
int find(int fa[],int x)
{
return x==fa[x]?x:fa[x]=find(fa,fa[x]);
}
void dfs(int node,int fe)
{
//printf("dfs(%d):ftr=%d,fe=%d\n",node,ftr[node],(bool)other[fe]);
depth[node]=depth[ftr[node]]+1;
fa[node]=node;
vst[node]=1;
for(int i=ptr[node];i;i=next[i])
if(i!=other[fe])
if(vst[succ[i]])
{
//printf("%d->%d:%d\n",node,succ[i],(bool)other[i]);
if(find(fa,succ[i]))
{
if(depth[fa[succ[i]]]>=depth[find(ff,succ[i])])
{
if(i<other[i])++ans;
while(depth[find(ff,node)]>depth[fa[succ[i]]])ff[ff[node]]=ftr[ff[node]];
++delta[node],++delta[succ[i]],delta[fa[succ[i]]]-=2;
//puts("Get");
}
}
}
else
{
ftr[succ[i]]=node;
if(other[i])ff[succ[i]]=node;
else ff[succ[i]]=succ[i];
dfs(succ[i],i);
}
fa[node]=ftr[node];
}
void query(int node,int fe)
{
vst[node]=1;
for(int i=ptr[node];i;i=next[i])
if(!vst[succ[i]])
{
query(succ[i],i);
delta[node]+=delta[succ[i]];
}
if(other[fe]&&delta[node])++ans;
}
int main()
{
freopen("bzoj2861.in","r",stdin);
freopen("bzoj2861.out","w",stdout);
fread(cp,1,4000000,stdin);
int n,m;
in(n),in(m);
int u,v,type;
for(int i=m;i--;)
{
in(u),in(v),in(type);
addedge(u,v);
if(type==2)
{
other[etot]=etot-1,other[etot-1]=etot;
addedge(v,u);
}
}
for(int i=n;i;--i)
if(!vst[i])
dfs(i,0);
memset(vst,0,sizeof(vst));
for(int i=n;i;--i)
if(!vst[i])
query(i,0);
printf("%d\n",ans);
}
总结:
①dfs非树边一个非常重要的性质是它必然是从当前点指向已访问过的点。要么是返祖边,要么是指向自己子树,要么是指向另一个dfs序考前的子树。