BZOJ3331 [BeiJing2013]压力[圆方树+树上差分]
圆方树新技能get。具体笔记见图连通性问题学习笔记。
这题求无向图的必经点,这个是一个固定套路:首先,一张连通的无向图中,每对点双和点双之间是以一个且仅一个割点连接起来的(如果超过一个就不能是割点了),那么,在一个点双内部,从出发点开始,要走到另外一个点双中,这个中间的割点就是一条必经之路(没有其他路可以绕,否则这就有一个环了),所以,路上所有割点都是必经点,而点双内部走的话,由点双的定义,是至少有两条点不相交的路径的(当然两个点的点双的话直接没有中间点),所以中间非割点是可经而不是必经的。
所以,构建圆方树,起始点和终止点构成的路径上所有圆点就是答案。然后,依题意做树上差分即可。
为什么我线性求lca还跑不过log的啊。。
圆方树构建的code大致是模仿zsy写的。。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #include<cmath> 6 #include<queue> 7 #define mst(x) memset(x,0,sizeof x) 8 #define dbg(x) cerr << #x << " = " << x <<endl 9 #define dbg2(x,y) cerr<< #x <<" = "<< x <<" "<< #y <<" = "<< y <<endl 10 using namespace std; 11 typedef long long ll; 12 typedef double db; 13 typedef pair<int,int> pii; 14 template<typename T>inline T _min(T A,T B){return A<B?A:B;} 15 template<typename T>inline T _max(T A,T B){return A>B?A:B;} 16 template<typename T>inline char MIN(T&A,T B){return A>B?(A=B,1):0;} 17 template<typename T>inline char MAX(T&A,T B){return A<B?(A=B,1):0;} 18 template<typename T>inline void _swap(T&A,T&B){A^=B^=A^=B;} 19 template<typename T>inline T read(T&x){ 20 x=0;int f=0;char c;while(!isdigit(c=getchar()))if(c=='-')f=1; 21 while(isdigit(c))x=x*10+(c&15),c=getchar();return f?x=-x:x; 22 } 23 const int N=1e5+7; 24 struct thxorz{ 25 int to[N<<2],nxt[N<<2],head[N<<1],tot; 26 inline void add(int x,int y){ 27 to[++tot]=y,nxt[tot]=head[x],head[x]=tot; 28 to[++tot]=x,nxt[tot]=head[y],head[y]=tot; 29 } 30 }G1,G2,Q; 31 int n,m,q; 32 int tag[N<<1],fa[N<<1],anc[N<<1],vis[N<<1]; 33 int Find(int x){return anc[x]==x?x:anc[x]=Find(anc[x]);} 34 #define y G2.to[j] 35 #define qy Q.to[j] 36 void tarjan_lca(int x,int fat){ 37 anc[x]=x;fa[x]=fat; 38 for(register int j=G2.head[x];j;j=G2.nxt[j])if(fat^y)tarjan_lca(y,x),anc[y]=x; 39 vis[x]=1;int tmp; 40 for(register int j=Q.head[x];j;j=Q.nxt[j])if(vis[qy])tmp=Find(qy),--tag[tmp],--tag[fa[tmp]]; 41 } 42 void dfs(int x){for(register int j=G2.head[x];j;j=G2.nxt[j])if(fa[x]^y)dfs(y),tag[x]+=tag[y];} 43 #undef y 44 #undef qy 45 #define y G1.to[j] 46 int dfn[N],low[N],stk[N],Top,tim,cnt; 47 void tarjan(int x){//promise:connected,without vertexes isolated. 48 dfn[x]=low[x]=++tim;stk[++Top]=x; 49 for(register int j=G1.head[x];j;j=G1.nxt[j]){ 50 if(!dfn[y]){//We needn't get the cut-vertexes,and cut-vertexes have nothing to do with v-DCCs. 51 tarjan(y),MIN(low[x],low[y]); 52 if(dfn[x]==low[y]){ 53 int tmp;++cnt; 54 do tmp=stk[Top--],G2.add(tmp,cnt);while(tmp^y); 55 G2.add(x,cnt); 56 } 57 } 58 else MIN(low[x],dfn[y]); 59 } 60 } 61 #undef y 62 int main(){//freopen("test.in","r",stdin);//freopen("test.ans","w",stdout); 63 cnt=read(n),read(m),read(q); 64 for(register int i=1,x,y;i<=m;++i)read(x),read(y),G1.add(x,y); 65 for(register int i=1;i<=n;++i)if(!dfn[i])Top=0,tarjan(i); 66 for(register int i=1,x,y;i<=q;++i)read(x),read(y),Q.add(x,y),++tag[x],++tag[y]; 67 tarjan_lca(1,0);dfs(1); 68 for(register int i=1;i<=n;++i)printf("%d\n",tag[i]); 69 return 0; 70 }
总结:图中简单路径问题,经过点的统计问题,考虑从点双和圆方树入手。
顺带一提,点双缩点一般配合圆方树食用,而边双直接缩成一棵树,比如这题如果问必经边的话,一定是割边是必经边(证明同理),只要缩边双后在树上直接差分即可。