Codeforces 571D - Campus(并查集+线段树+DFS 序,hot tea)

Codeforces 题目传送门 & 洛谷题目传送门

看到集合的合并,可以本能地想到并查集。

不过这题的操作与传统意义上的并查集不太一样,传统意义上的并查集一般是用来判断连通性的,而此题还需支持赋值、集合内整体加等操作,似乎就有些难处理。

我们不妨考虑此题的弱化版:没有第二类集合的版本。也就是要求支持合并两个集合,集合整体加某个数 \(x\),单点查询三个操作。

很明显集合整体加某个数 \(x\) 就相当于在并查集根节点处打一个 \(+x\) 的标记,查询就暴力跳父亲求出待询问点到根节点路径上所有点的标记之和。不过稍微想想就知道这个做法是假的,因为如果直接采用按秩合并会导致标记加的点集出现问题,举个例子,比方说我们要合并 \(x,y\) 两个集合,其中 \(x\) 为父亲,我们假设原本 \(x\) 点上有一个 \(+v\) 的标记,显然这个标记所管辖的点的集合是原本 \(x\) 所在的连通块,但经过我们这么一合并,我们默认每个标记管辖的点的集合是其子树,这也就意味着原本 \(y\) 所在的集合也被错误地打上了 \(+v\) 的标记,这样显然会出问题。我们考虑换个合并的思路,每次合并建立一个虚点 \(z\),并让 \(x,y\) 的父亲都为 \(z\),这样就能保证每次标记管辖的点集是正确的了。但这样还会导致一个问题,那就是树的深度可能很大,暴力条父亲会 TLE。故考虑把操作离线下来,先建出最后的森林出来并求出 DFS 序,每次修改用线段树在待修改点的子树内整体加 \(v\),查询就执行一次线段树上的单点查询即可,很明显森林里的点数最多为 \(n+q\),故总复杂度 \((n+q)\log(n+q)\)。

接下来考虑有第二类集合的版本,这个子树清空貌似维护起来有些困难,不过我们考虑换个思路,使用差分的思想,询问某个点 \(x\) 的值可以转化为此时 \(x\) 点的权值减去上一次 \(x\) 点被清空前 \(x\) 点的权值。故我们只用求出每次询问,带询问点上一次被清空的时间即可。那么这个东西怎么求呢?其实与弱化版维护的思路大同小异,先离线建出第二类集合的森林出来,每次清空一个节点 \(x\) 就相当于在 \(x\) 子树内所有点赋值 \(t\)(其中 \(t\) 为此次操作的时间),表示这些点上一次被清空是时间 \(t\),这个可以用区间赋值、单点查询的线段树维护。

到这里题目基本上已经做完了,不妨理理思路。我们先将询问离线下来,建出两类集合并查集所形成的森林,并对两个森林各进行一遍 DFS 求出每个点的 DFS 序,第二次扫描全部与第二类集合有关的操作,对于区间清空的操作就在子树对应 DFS 序的区间内执行区间赋值操作,如果遇到询问就进行一遍线段树上的单点查询,记录下待询问点上一次被清空的时间。第三次扫描全部与第一类集合有关的操作,对于区间加的操作就在子树对应 DFS 序的区间内执行区间加操作,如果遇到询问就进行一遍线段树上的单点查询,这样就可以求出答案了。

时间复杂度 \((n+q)\log(n+q)\)。

#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define fill0(a) memset(a,0,sizeof(a))
#define fill1(a) memset(a,-1,sizeof(a))
#define fillbig(a) memset(a,63,sizeof(a))
#define pb push_back
#define ppb pop_back
#define mp make_pair
template<typename T1,typename T2> void chkmin(T1 &x,T2 y){if(x>y) x=y;}
template<typename T1,typename T2> void chkmax(T1 &x,T2 y){if(x<y) x=y;}
typedef pair<int,int> pii;
typedef long long ll;
namespace fastio{
	#define FILE_SIZE 1<<23
	char rbuf[FILE_SIZE],*p1=rbuf,*p2=rbuf,wbuf[FILE_SIZE],*p3=wbuf;
	inline char getc(){return p1==p2&&(p2=(p1=rbuf)+fread(rbuf,1,FILE_SIZE,stdin),p1==p2)?-1:*p1++;}
	inline void putc(char x){(*p3++=x);}
	template<typename T> void read(T &x){
		x=0;char c=getchar();T neg=0;
		while(!isdigit(c)) neg|=!(c^'-'),c=getchar();
		while(isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=getchar();
		if(neg) x=(~x)+1;
	}
	template<typename T> void recursive_print(T x){if(!x) return;recursive_print(x/10);putc(x%10^48);}
	template<typename T> void print(T x){if(!x) putc('0');if(x<0) putc('-'),x=~x+1;recursive_print(x);}
	void print_final(){fwrite(wbuf,1,p3-wbuf,stdout);}
}
const int MAXN=5e5;
int n,qu;
struct solver{
	int bel[MAXN+5],siz[MAXN*2+5],tot;
	void init(){
		for(int i=1;i<=n;i++) bel[i]=i,siz[i]=1;
		tot=n;
	}
	int hd[MAXN*2+5],nxt[MAXN*2+5],to[MAXN*2+5],ec=0;
	void adde(int u,int v){to[++ec]=v;nxt[ec]=hd[u];hd[u]=ec;}
	void merge(int x,int y){
		tot++;adde(tot,bel[x]);adde(tot,bel[y]);
		siz[x]+=siz[y];siz[y]=0;bel[y]=0;bel[x]=tot;
	}
	int dfn[MAXN*2+5],sz[MAXN*2+5],tim=0;
	void dfs(int x){
		dfn[x]=++tim;sz[x]=1;
		for(int e=hd[x];e;e=nxt[e]) dfs(to[e]),sz[x]+=sz[to[e]];
	}
} s1,s2;
struct event{int opt,x,y;} q[MAXN+5];
vector<int> clr[MAXN+5];
ll res[MAXN+5];
struct seg1{
	struct node{int l,r;ll v,lz;} s[MAXN*8+5];
	void build(int k,int l,int r){
		s[k].l=l;s[k].r=r;if(l==r) return;
		int mid=l+r>>1;build(k<<1,l,mid);build(k<<1|1,mid+1,r);
	}
	void pushdown(int k){
		if(s[k].lz){
			s[k<<1].v+=s[k].lz*(s[k<<1].r-s[k<<1].l+1);
			s[k<<1].lz+=s[k].lz;
			s[k<<1|1].v+=s[k].lz*(s[k<<1|1].r-s[k<<1|1].l+1);
			s[k<<1|1].lz+=s[k].lz;
			s[k].lz=0;
		}
	}
	void modify(int k,int l,int r,int x){
		if(l<=s[k].l&&s[k].r<=r){
			s[k].v+=1ll*x*(s[k].r-s[k].l+1);s[k].lz+=x;
			return;
		} pushdown(k);int mid=s[k].l+s[k].r>>1;
		if(r<=mid) modify(k<<1,l,r,x);
		else if(l>mid) modify(k<<1|1,l,r,x);
		else modify(k<<1,l,mid,x),modify(k<<1|1,mid+1,r,x);
		s[k].v=s[k<<1].v+s[k<<1|1].v;
	}
	ll query(int k,int x){
		if(s[k].l==s[k].r) return s[k].v;
		pushdown(k);int mid=s[k].l+s[k].r>>1;
		if(x<=mid) return query(k<<1,x);
		else return query(k<<1|1,x);
	}
} st1;
struct seg2{
	struct node{int l,r,tag;} s[MAXN*8+5];
	void build(int k,int l,int r){
		s[k].l=l;s[k].r=r;if(l==r) return;
		int mid=l+r>>1;build(k<<1,l,mid);build(k<<1|1,mid+1,r);
	}
	void pushdown(int k){
		if(s[k].tag){
			s[k<<1].tag=s[k].tag;s[k<<1|1].tag=s[k].tag;
			s[k].tag=0;
		}
	}
	void modify(int k,int l,int r,int x){
		if(l<=s[k].l&&s[k].r<=r){s[k].tag=x;return;}
		pushdown(k);int mid=s[k].l+s[k].r>>1;
		if(r<=mid) modify(k<<1,l,r,x);
		else if(l>mid) modify(k<<1|1,l,r,x);
		else modify(k<<1,l,mid,x),modify(k<<1|1,mid+1,r,x);
	}
	int query(int k,int x){
		if(s[k].l==s[k].r) return s[k].tag;
		pushdown(k);int mid=s[k].l+s[k].r>>1;
		if(x<=mid) return query(k<<1,x);
		else return query(k<<1|1,x);
	}
} st2;
int main(){
	scanf("%d%d",&n,&qu);s1.init();s2.init();
	for(int i=1;i<=qu;i++){
		char opt[5];scanf("%s",opt+1);
		if(opt[1]=='U'){
			int x,y;scanf("%d%d",&x,&y);s1.merge(x,y);
			q[i].opt=1;q[i].x=x;q[i].y=y;
		} else if(opt[1]=='M'){
			int x,y;scanf("%d%d",&x,&y);s2.merge(x,y);
			q[i].opt=2;q[i].x=x;q[i].y=y;
		} else if(opt[1]=='A'){
			int x;scanf("%d",&x);
			q[i].opt=3;q[i].x=s1.bel[x];q[i].y=s1.siz[x];
		} else if(opt[1]=='Z'){
			int x;scanf("%d",&x);
			q[i].opt=4;q[i].x=s2.bel[x];
		} else{
			int x;scanf("%d",&x);
			q[i].opt=5;q[i].x=x;
		}
	}
	s1.tot++;for(int i=1;i<=n;i++) if(s1.siz[i]) s1.adde(s1.tot,s1.bel[i]);
	s2.tot++;for(int i=1;i<=n;i++) if(s2.siz[i]) s2.adde(s2.tot,s2.bel[i]);
	s1.dfs(s1.tot);s2.dfs(s2.tot);
	st1.build(1,1,s1.tot);st2.build(1,1,s2.tot);
	for(int i=1;i<=qu;i++){
		if(q[i].opt==4) st2.modify(1,s2.dfn[q[i].x],s2.dfn[q[i].x]+s2.sz[q[i].x]-1,i);
		if(q[i].opt==5){
			int tmp=st2.query(1,s2.dfn[q[i].x]);
			if(tmp) clr[tmp].pb(i);
		}
	}
	for(int i=1;i<=qu;i++){
		if(q[i].opt==3) st1.modify(1,s1.dfn[q[i].x],s1.dfn[q[i].x]+s1.sz[q[i].x]-1,q[i].y);
		if(q[i].opt==4){
			for(int j=0;j<clr[i].size();j++){
				int id=clr[i][j];
				res[id]-=st1.query(1,s1.dfn[q[id].x]);
			}
		}
		if(q[i].opt==5){
			res[i]+=st1.query(1,s1.dfn[q[i].x]);
			printf("%lld\n",res[i]);
		}
	}
	return 0;
}
上一篇:面试碰到这个算法:字母异位词分组


下一篇:Milk-Tea解析工具(DJ音乐解析)