bzoj3514 Codechef MARCH14 GERALD07加强版
考虑对于一个询问的右端点r
存在左边的一条边i,会使得存在区间i-1,r的边的时候,连通块数量比存在区间i,r的边少1
这样要查询的东西就转化成了区间l,r中,i小于l的边的数量,也就是有效边(会使连通块数量减一的边)的数量
答案就是n-ans
所以考虑维护最大删除时间生成树
每加入一条边时,如果成环,则找到边的编号最小的一条边删除掉,这条边就是r边要求的边i
lct维护生成树,边转化成点,具体实现细节见代码
注意:pushup的时候要判断一下儿子节点存不存在
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #include<queue> #include<stack> #include<cmath> using namespace std; typedef long long ll; const int maxn = 400100; const int INF = 1e9+7; int n,m,q,type,cnt,la; int st[maxn],val[maxn<<1]; int rt[maxn],sum[maxn<<4],lc[maxn<<4],rc[maxn<<4],add[maxn<<4],tot; struct E{ int u,v; }e[maxn]; struct Node{ int ch[2]; int fa; int mn,id; int rev; }t[maxn<<1]; int isroot(int x){ return (t[t[x].fa].ch[0]!=x)&&(t[t[x].fa].ch[1]!=x); } void pushup(int x){ t[x].mn=x; if(t[x].ch[0]){ // 要判断儿子节点是否存在 if(val[t[t[x].ch[0]].mn]<val[t[x].mn]) t[x].mn=t[t[x].ch[0]].mn; // 更新编号最小的点 } if(t[x].ch[1]){ if(val[t[t[x].ch[1]].mn]<val[t[x].mn]) t[x].mn=t[t[x].ch[1]].mn; } } void rever(int x){ t[x].rev^=1; swap(t[x].ch[0],t[x].ch[1]); } void pushdown(int x){ if(t[x].rev){ if(t[x].ch[0]) rever(t[x].ch[0]); if(t[x].ch[1]) rever(t[x].ch[1]); t[x].rev=0; } } void rotate(int x){ int y=t[x].fa,z=t[y].fa; int k=(t[y].ch[1]==x); if(!isroot(y)) t[z].ch[t[z].ch[1]==y]=x; t[x].fa=z; t[y].ch[k]=t[x].ch[k^1]; t[t[x].ch[k^1]].fa=y; t[x].ch[k^1]=y; t[y].fa=x; pushup(y); pushup(x); } int sta[maxn],top; void splay(int x){ top=0; int y=x; sta[++top]=y; while(t[y].fa){ y=t[y].fa; sta[++top]=y; } while(top) pushdown(sta[top--]); while(!isroot(x)){ int y=t[x].fa,z=t[y].fa; if(!isroot(y)){ (t[z].ch[1]==y)^(t[y].ch[1]==x)?rotate(x):rotate(y); }rotate(x); } pushup(x); } void access(int x){ for(int y=0;x;y=x,x=t[x].fa){ splay(x); t[x].ch[1]=y; pushup(x); } } void makeroot(int x){ access(x); splay(x); rever(x); } int findroot(int x){ access(x); splay(x); while(t[x].ch[0]){ pushdown(x); // findroot不要忘记pushdown x=t[x].ch[0]; } return x; } void split(int x,int y){ makeroot(x); access(y); splay(y); } void link(int x,int y){ makeroot(x); t[x].fa=y; } void cut(int x,int y){ makeroot(x); access(y); splay(y); t[y].ch[0]=t[x].fa=0; } int query(int x,int y){ split(x,y); // 直接把链拎出来询问 return t[y].mn; } void build(int &i,int l,int r){ i=++tot; if(l==r) return; int mid=(l+r)/2; build(lc[i],l,mid); build(rc[i],mid+1,r); } void mdf(int &i,int l,int r,int p){ // 主席树标记永久化 sum[++tot]=sum[i],add[tot]=add[i],lc[tot]=lc[i],rc[tot]=rc[i]; i=tot; sum[i]++; if(l==r){ add[i]++; return; } int mid=(l+r)/2; if(p<=mid) mdf(lc[i],l,mid,p); else mdf(rc[i],mid+1,r,p); } int qry(int i,int ad,int l,int r,int x,int y){ if(l==x&&r==y){ return sum[i]+ad*(r-l+1); } int mid=(l+r)/2; if(y<=mid) return qry(lc[i],ad+add[i],l,mid,x,y); else if(x>mid) return qry(rc[i],ad+add[i],mid+1,r,x,y); else{ return qry(lc[i],ad+add[i],l,mid,x,mid)+qry(rc[i],ad+add[i],mid+1,r,mid+1,y); } } void pre(){ cnt=n; int u,v; for(int i=1;i<=m;i++){ u=e[i].u,v=e[i].v; if(u==v){ st[i]=i; continue; } if(findroot(u)==findroot(v)){ int tmp=query(u,v); int x=val[tmp]; st[i]=x; cut(e[x].u,tmp); cut(e[x].v,tmp); } ++cnt; t[cnt].mn=cnt; val[cnt]=i; // val记录的是边转化成lct中的点的编号对应的原边的编号 link(u,cnt); link(v,cnt); } for(int i=1;i<=m;i++){ rt[i]=rt[i-1]; mdf(rt[i],0,m,st[i]); } } ll read(){ ll s=0,f=1; char ch=getchar(); while(ch<'0' || ch>'9'){ if(ch=='-') f=-1; ch=getchar(); } while(ch>='0' && ch<='9'){ s=s*10+ch-'0'; ch=getchar(); } return s*f;} int main(){ n=read(),m=read(),q=read(),type=read(); val[0]=INF; for(int i=1;i<=n;i++) t[i].mn=i,val[i]=INF; // 不是边转化成的点,val值要赋为正无穷,防止被更新 for(int i=1;i<=m;i++) e[i].u=read(),e[i].v=read(); build(rt[0],0,m); pre(); la=0; int u,v; for(int i=1;i<=q;i++){ u=read(),v=read(); if(type==1) u^=la,v^=la; la=n-(qry(rt[v],0,0,m,0,u-1)-qry(rt[u-1],0,0,m,0,u-1)); printf("%d\n",la); } return 0; }