「BalticOI 2021 Day1」Inside information

「BalticOI 2021 Day1」Inside information

题目大意

n 个集合 Si ,一开始 Si={i}

执行 m 个操作:

  1. 选择 u,v ,令 Su,Sv:=SuSv
  2. 对于 u,x,回答 [xSu]
  3. 对于 x,回答 i[xSi]

保证操作 1 恰好出现 n1 次,且所有边 (u,v) 构成一棵树。

分析

题目保证或操作构成一棵树,考虑一个熟悉的问题:

每次选择两个点连边,维护连通块中的点集。

用并查集+线段树合并处理。

在这道题中,点之间的关系无法用并查集处理,但依然可以考虑线段树合并。

容易发现,每次被合并的树 treeu,treev,它们的节点集合都是 blocku,blockv 的子集,其中 blocku 表示 u 所在连通块的点集。

而由上面的提到的经典问题,我们知道合并 blocku,blockv 的线段树合并复杂度为 O(nlogn)

而合并 treeu,treev 的线段树合并复杂度不高于合并 blocku,blockv,因此不高于 O(nlogn)

因此 2 操作可以直接用线段树合并处理,由于一棵树可能被合并多次,需要进行可持久化操作。

对于第二个问题,考虑合并的关系构成一个 DAG,每次合并会新建一个 DAG 节点,3 操作的答案就是当前节点在 DAG 反图上可达的节点数目。

容易发现在反图上用线段树合并维护可达点集的复杂度分析与正向的合并相同,而查询当前时刻的节点数量,可以改为查询编号 i 的节点数量,其中 i 表示在查询时的编号最大的 DAG 节点。

只需要进行正反两次可持久化线段树合并,复杂度为 O(nlogn)

bool Mbe;
const int N=1.2e5+10,M=N*40,INF=1e9+10;

int n,m;
int rt[N*2],ls[M],rs[M],s[M],cnt;
void Ins(int &p,int l,int r,int x){
	p=++cnt,s[p]=1;
	if(l==r) return;
	int mid=(l+r)>>1;
	x<=mid?Ins(ls[p],l,mid,x):Ins(rs[p],mid+1,r,x);
}
int Union(int x,int y) {
	if(!x || !y) return x|y;
	int u=++cnt;
	ls[u]=Union(ls[x],ls[y]);
	rs[u]=Union(rs[x],rs[y]);
	s[u]=s[x]+s[y];
	return u;
}
int Que(int p,int l,int r,int x){
	if(!p) return 0;
	if(l==r) return 1;
	int mid=(l+r)>>1;
	return x<=mid?Que(ls[p],l,mid,x):Que(rs[p],mid+1,r,x);
}
int Que(int p,int l,int r,int ql,int qr) {
	if(!p || ql>qr) return 0;
	if(ql<=l && r<=qr) return s[p];
	int mid=(l+r)>>1,res=0;
	if(ql<=mid) res+=Que(ls[p],l,mid,ql,qr);
	if(qr>mid) res+=Que(rs[p],mid+1,r,ql,qr);
	return res;
}

char op[3];
int I[N],J[N];
vector <int> G[N*2];
int QX[N],QY[N];

bool Med;
int main() {
	n=rd(),m=rd()+n-1;
	rep(i,1,n) Ins(rt[i],1,n,i),J[i]=i;
	int c=0,t=n;
	rep(i,1,m) {
		scanf("%s",op);
		if(*op=='S') {
			int a=rd(),b=rd();
			++t;
			if(!I[a]) I[a]=t;
			else G[J[a]].pb(t);
			if(!I[b]) I[b]=t;
			else G[J[b]].pb(t);
			rt[t]=Union(rt[J[a]],rt[J[b]]);
			J[a]=J[b]=t;
		} else if(*op=='Q') {
			int a=J[rd()],b=rd();
			QX[++c]=-Que(rt[a],1,n,b);
		} else {
			QX[++c]=rd(),QY[c]=t;
		}
	}
	rep(i,1,::cnt) ls[i]=rs[i]=s[i]=0;
	cnt=0;
	drep(i,n*2-1,n+1) {
		int u=i-n;
		Ins(rt[u],1,n,u);
		for(int j:G[i]) {
			int v=j-n;
			rt[u]=Union(rt[u],rt[v]);
		}
	}
	rep(i,1,c) {
		if(QX[i]<=0) puts(QX[i]?"yes":"no");
		else printf("%d\n",Que(rt[I[QX[i]]-n],1,n,1,QY[i]-n)+1);
	}
}
posted @   chasedeath  阅读(290)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示