BZOJ 3514 Codechef MARCH14 GERALD07加强版

题目链接:Codechef MARCH14 GERALD07加强版

  看到这种求连边求联通块个数的题,大概思路就是计算一下有多少条边连接了两个不同的联通块,然后用总点数减一下。

  然后我们该如何判断一条边是有效边呢?我们可以先用\(LCT\)来维护原图,当形成环的时候就把换上最早出现的边给弹掉。我们记\(a_i\)表示第\(i\)条边弹掉的边的编号(如果没有就为\(0\)),那么当我们只保留编号在\([l,r]\)内的边时,如果这其中的一条边\(x\)满足\(a_x<l\),那么这条边才是有用的。即这条边连接了两个联通块,会使得联通块个数\(-1\)。

  于是使用\(LCT\)预处理之后用主席树维护一下\(a\)数组就做完了。

  下面贴代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define File(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
#define isroot(x) (x!=s[fa[x]][0] && x!=s[fa[x]][1])
#define maxn 200010
#define MAXN maxn*20
#define INF (1<<29)

using namespace std;
typedef long long llg;

struct data{
	int u,v;
}a[maxn];
int n,m,q,ty,ff[maxn],d[maxn<<1],val[maxn<<1],tt;
int fa[maxn<<1],s[maxn<<1][2],minv[maxn<<1];
int rt[maxn],sumv[MAXN],le[MAXN],ri[MAXN],L,ans;
bool rev[maxn<<1];

int getint(){
	int w=0;bool q=0;
	char c=getchar();
	while((c>'9'||c<'0')&&c!='-') c=getchar();
	if(c=='-') c=getchar(),q=1;
	while(c>='0'&&c<='9') w=w*10+c-'0',c=getchar();
	return q?-w:w;
}

int find(int x){return ff[ff[x]]==ff[x]?ff[x]:ff[x]=find(ff[x]);}
void update(int u){
	int l=s[u][0],r=s[u][1]; minv[u]=u;
	if(val[minv[l]]<val[minv[u]]) minv[u]=minv[l];
	if(val[minv[r]]<val[minv[u]]) minv[u]=minv[r];
}

void rotate(int u){
	int p=fa[u],g=fa[p];
	bool l=(u==s[p][1]),r=!l;
	if(!isroot(p)) s[g][p==s[g][1]]=u;
	fa[s[u][r]]=p; s[p][l]=s[u][r];
	s[u][r]=p; fa[p]=u; fa[u]=g;
	update(p); update(u);
}

void splay(int u){
	int ld=0; d[ld=1]=u;
	for(int i=u;!isroot(i);i=fa[i]) d[++ld]=fa[i];
	for(int i=ld,x;x=d[i],i;i--)
		if(rev[x]){
			rev[s[x][0]]^=1,rev[s[x][1]]^=1;
			swap(s[x][0],s[x][1]); rev[x]=0;
		}
	while(!isroot(u)){
		int p=fa[u],g=fa[p];
		if(!isroot(p)){
			if((u==s[p][1])^(p==s[g][1])) rotate(u);
			else rotate(p);
		}
		rotate(u);
	}
}

void access(int u){for(int t=0;u;t=u,u=fa[u]) splay(u),s[u][1]=t,update(u);}
void makert(int u){access(u); splay(u); rev[u]^=1;}
void link(int x,int y){makert(x); fa[x]=y;}
void cut(int x,int y){
	makert(x); access(y),splay(y);
	s[y][0]=fa[x]=0; update(y);
}

int query1(int x,int y){
	makert(x); access(y); splay(y);
	return minv[y];
}

int add(int u,int l,int r){
	int now=++tt,mid=(l+r)>>1;
	sumv[now]=sumv[u]+1;
	le[now]=le[u]; ri[now]=ri[u];
	if(l!=r){
		if(L<=mid) le[now]=add(le[u],l,mid);
		else ri[now]=add(ri[u],mid+1,r);
	}
	return now;
}

int query2(int u,int v){
	int l=0,r=m,mid,now=0;
	while(l!=r){
		mid=(l+r)>>1;
		if(L<=mid) u=le[u],v=le[v],r=mid;
		else now+=sumv[le[u]]-sumv[le[v]],u=ri[u],v=ri[v],l=mid+1;
	}
	return now;
}

int main(){
	File("a");
	n=getint(); m=getint(); q=getint(); ty=getint();
	for(int i=1;i<=n;i++) ff[i]=i,val[i]=INF; val[0]=INF;
	for(int i=1,u,v,x;i<=m;i++){
		a[i].u=u=getint(); a[i].v=v=getint(); x=n,val[i+n]=i;
		if(find(u)!=find(v)) ff[find(u)]=find(v);
		else if(u!=v) x=query1(u,v),cut(a[x-n].u,x),cut(a[x-n].v,x);
		if(u!=v) link(u,i+n),link(v,i+n); else x+=m;
		L=x-n,rt[i]=add(rt[i-1],0,m);
	}
	while(q--){
		int l=getint(),r=getint();
		if(ty) l^=ans,r^=ans; L=l;
		printf("%d\n",ans=n-query2(rt[r],rt[l-1]));
	}
	return 0;
}

  这种码农题写完就过的感觉真爽(爆数组\(RE\)的那一发不算)

posted @ 2017-03-06 17:18  lcf2000  阅读(155)  评论(0编辑  收藏  举报