[bzoj2861] 双向边定向为单向边 解题报告
2016-09-11 20:28
295 查看
这题搞了好久。。
首先一条双向边(u,v)可以变成单向边的条件是存在一个经过这条边的环,只需要按这个环的方向把这个环上的所有双向边变成单向就可以了,如果这个环上都是双向边,就随便定一个方向就可以了。
所以我们考虑将双向边拆成两条单向边dfs,这样树边至少存在一个向下的方向,但也有可能是向下的单向边。
但是考虑非树边的时候首先注意到一件事情,就是两条非树边之间可能互相影响,就是说一条非树边可能不能与树边构成环,但是可能与另几条非树边构成环。但是,其实这种影响必然是按dfs序从先到后的。
对于一条非树边(u,v),如果(lca,v)的树边中不存在单向边,那么我们就认为这条非树边与从u到v的树边构成了环,并且把(u,lca)的单向边清为双向边。(其实就是把环缩点。。)
这样的话如果(u,v)并不能构成环,那么它在dfs的过程中之后也不会构成环了,因为(lca,v)已经被dfs过了,只有在lca在v一侧的子树中的出边才能消除其中的单向边,而它们已经被dfs过了。(所以说是没有后效性的!)
所以我们可以在dfs过程中顺便tarjan lca,然后再用一个并查集维护一条边上面第一条单向边即可。
总结:
①dfs非树边一个非常重要的性质是它必然是从当前点指向已访问过的点。要么是返祖边,要么是指向自己子树,要么是指向另一个dfs序考前的子树。
首先一条双向边(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 ,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 ; int depth ; bool vst ; int ftr ; int fa ,ff ; 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序考前的子树。
相关文章推荐
- BZOJ2861 : 双向边定向为单向边
- BZOJ 3107 [cqoi 2013] DP 解题报告
- [bzoj2555]substring 解题报告
- BZOJ 1379 [Baltic 2001] 解题报告
- BZOJ 3319 并查集 解题报告
- 剑指offer解题报告(Java版)——二叉搜索树转换为双向链表 27
- [bzoj4544]椭圆上的整点 解题报告
- BZOJ 1483 [HNOI 2009] 启发式合并链表 解题报告
- BZOJ4444: [Scoi2015]国旗计划 解题报告
- [bzoj3462]dzy loves math II 解题报告
- BZOJ 1052 二分答案 解题报告
- BZOJ 1356 计算几何 解题报告
- BZOJ 1475 最小割 解题报告
- BZOJ 3971 Матрёшка 解题报告
- [bzoj4314] 倍数?倍数! 解题报告
- BZOJ 2527 [Poi 2011] 整体二分 解题报告
- [bzoj3202]项链 解题报告
- BZOJ 1264 树状数组+DP 解题报告
- BZOJ 1419 DP 解题报告
- BZOJ 4302 Buildings 解题报告