BZOJ 3514 Codechef MARCH14 GERALD07加强版 Link-Cut-Tree+划分树
题目大意:
给定n个点m条边的无向图。求问当图中仅仅有【编号在[l,r]区间内】的边存在时图中的联通块个数 强制在线
注意联通块是指联通了就是同一块,不是Tarjan求的那种块
看到这题的那一刻我就想小便有木有0.0 这尼玛怎么做?可持久化并查集? 暴力? 分块乱搞? 。。。
后来看了HZWER大神的博客才知道这样的巧妙的算法0.0 太强大了
直接复制wulala的题解 讲得非常清楚 不累述了
然后对于每一个询问,我们的答案就是对l~r中ntr小于l的边求和,并用n减去这个值
正确性能够YY一下:
假设一条边的ntr >= l,那么显然他能够与从l ~ r中的边形成环,那么它对答案没有贡献
反之假设一条边的ntr < l那么它与从l ~ r中的边是不能形成环的。那么他对答案的贡献为-1
对于查询从l ~ r中有多少边的ntr小于l,我反正是用的函数式线段树
这个真是太强大了0.0 假设这条边踢掉的最早的边也在[l,r]区间内 那么增加这条边一定对图的连通性没有影响 否则就会连接两个联通块 导致ans--
至于求l~r中有多少边的ntr小于l我用的是划分树 蒟蒻不会写主席树肿莫破。。
。
ntr。寝取り,果然是个够劲的名字。
。
。 于是为了保持这样的美好的意境我也牺牲了划分树中的a数组改成了ntr。。
。
把这个美好的意境传承下去吧。。
。
另外这题有自环 自环的话相当于自己ntr自己 直接记录ntr之后就返回好了
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define M 200200 #define INF 2147483647 using namespace std; struct edges{ int x,y; }e[M]; struct abcd{ abcd *fa,*ls,*rs; int num,minnum; bool rev_mark; abcd(int x); void Reverse(); void Push_Up(); void Push_Down(); }*null=new abcd(INF),*tree[M<<1]; abcd :: abcd(int x) { fa=ls=rs=null; num=minnum=x; rev_mark=0; } void abcd :: Reverse() { rev_mark^=1; swap(ls,rs); } void abcd :: Push_Up() { minnum=min(ls->minnum,rs->minnum); minnum=min(minnum,num); } void abcd :: Push_Down() { if(fa->ls==this||fa->rs==this) fa->Push_Down(); if(rev_mark) { ls->Reverse(); rs->Reverse(); rev_mark=0; } } void Zig(abcd *x) { abcd *y=x->fa; y->ls=x->rs; x->rs->fa=y; x->rs=y; x->fa=y->fa; if(y==y->fa->ls) y->fa->ls=x; else if(y==y->fa->rs) y->fa->rs=x; y->fa=x; y->Push_Up(); } void Zag(abcd *x) { abcd *y=x->fa; y->rs=x->ls; x->ls->fa=y; x->ls=y; x->fa=y->fa; if(y==y->fa->ls) y->fa->ls=x; else if(y==y->fa->rs) y->fa->rs=x; y->fa=x; y->Push_Up(); } void Splay(abcd *x) { x->Push_Down(); while(x->fa->ls==x||x->fa->rs==x) { abcd *y=x->fa,*z=y->fa; if(x==y->ls) { if(y==z->ls) Zig(y); Zig(x); } else { if(y==z->rs) Zag(y); Zag(x); } } x->Push_Up(); } void Access(abcd *x) { abcd *y=null; while(x!=null) { Splay(x); x->rs=y; x->Push_Up(); y=x; x=x->fa; } } abcd* Find_Root(abcd *x) { while(x->fa!=null) x=x->fa; return x; } void Move_To_Root(abcd *x) { Access(x); Splay(x); x->Reverse(); } void Link(abcd *x,abcd *y) { Move_To_Root(x); x->fa=y; } void Cut(abcd *x,abcd *y) { Move_To_Root(x); Access(y); Splay(y); x->fa=null; y->ls=null; y->Push_Up(); } int Query(abcd *x,abcd *y) { Move_To_Root(x); Access(y); Splay(y); return y->minnum; } int n,m,q,type,ans; int ntr[M],b[M],c[M],s[20][M]; void Insert(int p) { if(e[p].x==e[p].y) { ntr[p]=p; return ; } abcd *x=tree[e[p].x],*y=tree[e[p].y]; if( Find_Root(x)==Find_Root(y) ) { int temp=Query(x,y); ntr[p]=temp; Cut(tree[n+temp],tree[e[temp].x]); Cut(tree[n+temp],tree[e[temp].y]); free(tree[n+temp]); } tree[n+p]=new abcd(p); Link(tree[n+p],tree[e[p].x]); Link(tree[n+p],tree[e[p].y]); } void Build_Tree(int l,int r,int dpt) { int i,mid=l+r>>1; int l1=l,l2=mid+1; int left=mid-l+1; if(l==r) return ; for(i=l;i<=r;i++) left-=(ntr[i]<c[mid]); for(i=l;i<=r;i++) { if(ntr[i]<c[mid]||ntr[i]==c[mid]&&left) b[l1++]=ntr[i],s[dpt][i]=(i==l?1:s[dpt][i-1]+1),left-=(ntr[i]==c[mid]); else b[l2++]=ntr[i],s[dpt][i]=(i==l?0:s[dpt][i-1]); } memcpy( ntr+l , b+l , sizeof(ntr[0])*(r-l+1) ); Build_Tree(l,mid,dpt+1); Build_Tree(mid+1,r,dpt+1); } int Get_Ans(int l,int r,int dpt,int x,int y,int val) { int mid=l+r>>1; int l1=(x==l?0:s[dpt][x-1]),l2=s[dpt][y]; if(x>y) return 0; if(l==r) return ntr[mid]<val; if(val<=c[mid]) return Get_Ans(l,mid,dpt+1,l+l1,l+l2-1,val); else return l2-l1+Get_Ans(mid+1,r,dpt+1,(mid+1)+(x-l-l1),(mid+1)+(y-l+1-l2)-1,val); } int main() { int i,x,y; cin>>n>>m>>q>>type; for(i=1;i<=n;i++) tree[i]=new abcd(INF); for(i=1;i<=m;i++) scanf("%d%d",&e[i].x,&e[i].y),Insert(i); memcpy(c+1,ntr+1,sizeof(c[0])*m); sort(c+1,c+m+1); Build_Tree(1,m,0); for(i=1;i<=q;i++) { scanf("%d%d",&x,&y); x^=ans*type;y^=ans*type; ans=n-Get_Ans(1,m,0,x,y,x); printf("%d\n", ans ); } }