Codeforces 1479 D Odd Mineral Resource

本文介绍了如何通过树上莫队算法优化查询路径信息,同时处理修改操作的困难,外层套分块策略。另一种解决方案是采用随机化方法,利用矿物标签的异或特性判断路径中奇数矿物的存在概率,结合值域主席树实现高效查询。两种方法的时间复杂度和空间复杂度对比鲜明。

题面
此题树上不带修,查询路径信息,时限较长,可以树上莫队。利用树上莫队的传统方法,先树分块,对询问排序,然后再执行类似普通莫队的操作即可。但这里的修改操作并不好处理,于是在外层套一分块。每次修改就先修改单点的奇偶性,再修改块内奇数的总数。查询时用普通分块查询方法即可。
时间复杂度 O ( ( n + q ) n ) O((n+q) \sqrt{n}) O((n+q)n ),空间复杂度 O ( n + q ) O(n+q) O(n+q)

#include<stdio.h>
#include<algorithm>
#include<vector>
using namespace std;
#define R register int
#define L long long
#define I inline
#define N 300001
#define S 550
vector<int>G[N];
int a[N],pos[600001],b[546],dep[N],in[N],out[N],ans[N],h[N],ct[N],f[N],Top[N],sz[N];
bool tag[N],vc[N];
struct Query{
	int lf,rt,Start,End,id,Ql,Qr;
	I friend bool operator<(Query x,Query y){
		if(x.lf/S!=y.lf/S){
			return x.lf<y.lf;
		}
		return x.rt<y.rt;
	}
}q[N];
I void PreDFS(R x,R F,int&ct){
	ct++;
	pos[ct]=x;
	sz[x]=1;
	in[x]=ct;
	dep[x]=dep[F]+1;
	f[x]=F;
	for(vector<int>::iterator T=G[x].begin();T!=G[x].end();T++){
		if(*T!=F){
			PreDFS(*T,x,ct);
			sz[x]+=sz[*T];
			if(sz[*T]>sz[h[x]]){
				h[x]=*T;
			}
		}
	}
	ct++;
	out[x]=ct;
	pos[ct]=x;
}
I void ReDFS(R x,R t){
	Top[x]=t;
	if(h[x]!=0){
		ReDFS(h[x],t);
		for(vector<int>::iterator T=G[x].begin();T!=G[x].end();T++){
			if(*T!=f[x]&&*T!=h[x]){
				ReDFS(*T,*T);
			}
		}
	}
}
I int LCA(R x,R y){
	while(Top[x]!=Top[y]){
		if(dep[Top[x]]>dep[Top[y]]){
			x=f[Top[x]];
		}else{
			y=f[Top[y]];
		}
	}
	return dep[x]<dep[y]?x:y;
}
I void Add(int x){
	vc[x]^=true;
	if(vc[x]==true){
		b[x/S]++;
	}else{
		b[x/S]--;
	}
}
I int GetAns(int l,int r){
	int bl=l/S,br=r/S;
	if(bl==br){
		for(R i=l;i<=r;i++){
			if(vc[i]==true){
				return i;
			}
		}
		return-1;
	}
	for(R i=bl+1;i!=br;i++){
		if(b[i]!=0){
			for(R j=i*S;j!=(i+1)*S;j++){
				if(vc[j]==true){
					return j;
				}
			}
		}
	}
	for(R i=l;i!=(bl+1)*S;i++){
		if(vc[i]==true){
			return i;
		}
	}
	for(R i=br*S;i<=r;i++){
		if(vc[i]==true){
			return i;
		}
	}
	return-1;
}
int main(){
	int n,m,x,y;
	scanf("%d%d",&n,&m);
	for(R i=1;i<=n;i++){
		scanf("%d",a+i);
	}
	for(R i=1;i!=n;i++){
		scanf("%d%d",&x,&y);
		G[x].push_back(y);
		G[y].push_back(x);
	}
	x=0;
	PreDFS(1,0,x);
	ReDFS(1,1);
	for(R i=0;i!=m;i++){
		scanf("%d%d%d%d",&q[i].Start,&q[i].End,&q[i].Ql,&q[i].Qr);
		R st=q[i].Start,ed=q[i].End;
		R t=LCA(st,ed);
		q[i].id=i;
		if(t==st){
			q[i].lf=in[t];
			q[i].rt=in[ed];
		}else if(t==ed){
			q[i].lf=in[t];
			q[i].rt=in[st];
		}else if(out[st]<in[ed]){
			q[i].lf=out[st];
			q[i].rt=in[ed];
		}else{
			q[i].lf=out[ed];
			q[i].rt=in[st];
		}
	}
	sort(q,q+m);
	R l=1,r=0;
	for(R i=0;i!=m;i++){
		R tem,curr=q[i].rt,curl=q[i].lf;
		while(r<curr){
			r++;
			tem=pos[r];
			Add(a[tem]);
			tag[tem]^=true;
		}
		while(l>curl){
			l--;
			tem=pos[l];
			Add(a[tem]);
			tag[tem]^=true;
		}
		while(r>curr){
			tem=pos[r];
			Add(a[tem]);
			tag[tem]^=true;
			r--;
		}
		while(l<curl){
			tem=pos[l];
			Add(a[tem]);
			tag[tem]^=true;
			l++;
		}
		R lca=LCA(q[i].Start,q[i].End);
		if(lca!=q[i].Start&&q[i].End!=lca){
			Add(a[lca]);
			ans[q[i].id]=GetAns(q[i].Ql,q[i].Qr);
			Add(a[lca]);
		}else{
			ans[q[i].id]=GetAns(q[i].Ql,q[i].Qr);
		}
	}
	for(R i=0;i!=m;i++){
		printf("%d\n",ans[i]);
	}
	return 0;
}

莫队虽然勉强可过,但效率不高。此题可以采用随机化的算法。给每种矿物一个随机标签,若路径上属于给定区间的标签异或和不为 0 0 0,则一定有个数为奇数的矿物;若异或和为 0 0 0,则只有极小的概率存在个数为奇数的矿物。于是对原树建值域主席树,每次询问查询区间异或和即可。
时间复杂度 O ( ( n + q ) log ⁡ 2 n ) O((n+q) \log_2n) O((n+q)log2n),空间复杂度 O ( n log ⁡ 2 n ) O(n \log_2n) O(nlog2n)

#include<stdio.h>
#include<vector>
#include<algorithm>
#include<time.h>
using namespace std;
#define R register int
#define L long long
#define I inline
#define N 300001
struct SegmentNode{
	int Ls,Rs;
	L XorSum;
}t[9999999];
int a[N],f[N],h[N],sz[N],dep[N],Top[N],ct;
I void GetNode(int&x){
	ct++;
	x=ct;
}
L v[N];
I void Insert(int p1,int p2,int lf,int rt,const int x){
	t[p2].XorSum=v[x]^t[p1].XorSum;
	if(lf!=rt){
		int mid=lf+rt>>1;
		if(x>mid){
			GetNode(t[p2].Rs);
			t[p2].Ls=t[p1].Ls;
			Insert(t[p1].Rs,t[p2].Rs,mid+1,rt,x);
		}else{
			GetNode(t[p2].Ls);
			t[p2].Rs=t[p1].Rs;
			Insert(t[p1].Ls,t[p2].Ls,lf,mid,x);
		}
	}
}
I int GetAns(int p1,int p2,int p3,int p4,int lf,int rt,const int l,const int r){
	if(l<=lf&&rt<=r&&(t[p1].XorSum^t[p2].XorSum^t[p3].XorSum^t[p4].XorSum)==0){
		return-1;
	}
	if(lf==rt){
		return rt;
	}
	int mid=lf+rt>>1,q=-1;
	if(l<=mid){
		q=GetAns(t[p1].Ls,t[p2].Ls,t[p3].Ls,t[p4].Ls,lf,mid,l,r);
	}
	if(q==-1&&r>mid){
		return GetAns(t[p1].Rs,t[p2].Rs,t[p3].Rs,t[p4].Rs,mid+1,rt,l,r);
	}
	return q;
}
I L GetRand(){
	return(L)rand()<<45|(L)rand()<<30|rand()<<15;
}
vector<int>G[N];
I void PreDFS(int x,int F,int&n){
	dep[x]=dep[F]+1;
	f[x]=F;
	sz[x]=1;
	Insert(F,x,1,n,a[x]);
	for(vector<int>::iterator T=G[x].begin();T!=G[x].end();T++){
		if(*T!=F){
			PreDFS(*T,x,n);
			sz[x]+=sz[*T];
			if(sz[*T]>sz[h[x]]){
				h[x]=*T;
			}
		}
	}
}
I void ReDFS(int x,int t){
	Top[x]=t;
	if(h[x]!=0){
		ReDFS(h[x],t);
		for(vector<int>::iterator T=G[x].begin();T!=G[x].end();T++){
			if(*T!=f[x]&&*T!=h[x]){
				ReDFS(*T,*T);
			}
		}
	}
}
I int GetLCA(int x,int y){
	while(Top[x]!=Top[y]){
		if(dep[Top[x]]>dep[Top[y]]){
			x=f[Top[x]];
		}else{
			y=f[Top[y]];
		}
	}
	return dep[x]<dep[y]?x:y;
}
int main(){
	int n,q,x,y,l,r,z;
	scanf("%d%d",&n,&q);
	ct=n;
	srand((int)time(0));
	for(R i=1;i<=n;i++){
		scanf("%d",a+i);
		v[i]=GetRand();
	}
	for(R i=1;i!=n;i++){
		scanf("%d%d",&x,&y);
		G[x].push_back(y);
		G[y].push_back(x); 
	}
	PreDFS(1,0,n);
	ReDFS(1,1);
	for(R i=0;i!=q;i++){
		scanf("%d%d%d%d",&x,&y,&l,&r);
		z=GetLCA(x,y);
		printf("%d\n",GetAns(x,y,z,f[z],1,n,l,r));
	}
	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值