莫队应用(带修树上莫队和值域分块)

带修树上莫队

例题luogu 糖果公园

解题

该题理解题意后即为求一条简单路径上

\sum _c val_c *\sum_{i=1}^{cnt_c}w_i

用树上莫队转化为链式结构解决,发现做两次贡献为0可以用 异或 解决,再用带修莫队维护即可

注意一些小细节即可

代码及注释

#include<bits/stdc++.h>
#define int long long
#define B(x) ((x+bs-1)/bs)
using namespace std;
const int N=2e5+10,M=26;
int f[N][M+1],h[N*2],idx,v[N],w[N];
int dfn[N],timeset,st[N],en[N],a[N];
int temp,vis[N],cnt[N];
int dep[N];
int n,m,Q,mcnt,qcnt;
int bs;
struct ques{
	int l,r,id,time,lca;
}q[N*2];
struct modify{
	int x,p;
}modi[N*2];
struct edge{
	int v,next;
}e[2*N];
void con(int u,int v){
	e[++idx].v=v;
	e[idx].next=h[u];
	h[u]=idx;
}
void dfs(int u,int fa){
	dep[u]=dep[fa]+1;
	st[u]=++timeset;
	dfn[timeset]=u;
	f[u][0]=fa;
	for(int i=1;i<M;i++){
		f[u][i]=f[f[u][i-1]][i-1];
	}
	for(int i=h[u];i;i=e[i].next){
		int v=e[i].v;
		if(v!=fa) dfs(v,u);
	}
	en[u]=++timeset;
	dfn[timeset]=u;
}//树上莫队及LCA预处理
int lca(int x,int y){
	if(dep[x]<dep[y]) swap(x,y);
	for(int i=M-1;i>=0;i--){
		if(dep[f[x][i]]>=dep[y]) x=f[x][i];
	}
	if(x==y) return x;
	for(int i=M-1;i>=0;i--){
		if(f[x][i]!=f[y][i]) {
			x=f[x][i],y=f[y][i];
		}
	}
	return f[x][0];
}
bool cmp(ques x,ques y){
	if(B(x.l)!=B(y.l)) return x.l<y.l;
	if(B(x.r)!=B(y.r)) return x.r<y.r;
	return x.time<y.time;
}//询问排序
void add(int x){
	cnt[x]++;
	temp+=v[x]*w[cnt[x]];
}
void del(int x) {
	temp-=v[x]*w[cnt[x]];
	cnt[x]--;
}
void lcd(int pos){
	vis[pos]^=1;
	if(vis[pos]==1) add(a[pos]);
	else del(a[pos]);
}//括号序用异或处理两次进入
void modif(int t) {
	if(vis[modi[t].p]) lcd(modi[t].p),swap(modi[t].x,a[modi[t].p]),lcd(modi[t].p);
	//删除一个节点只需要让他再异或一次即可,添加亦然
	else swap(modi[t].x,a[modi[t].p]);
}//修改数组
int ans[N];
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>n>>m>>Q;
	bs=pow(2*n,2.0/3.0);
	for(int i=1;i<=m;i++){
		cin>>v[i];
	}
	for(int i=1;i<=n;i++){
		cin>>w[i];
	}
	for(int i=1;i<n;i++) {
		int u,v;
		cin>>u>>v;
		con(u,v),con(v,u);
	}
	dfs(1,0);
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	while(Q--){
		int opt,x,y;
		cin>>opt>>x>>y;
		if(opt==0) modi[++mcnt]={y,x};
		else{
			int lc=lca(x,y);
			if(lc==x||lc==y) {
				if(st[x]>st[y]) swap(x,y);
				q[++qcnt]={st[x],st[y],qcnt,mcnt,0};
			}
			else {
				if(en[x]>st[y]) swap(x,y);
				q[++qcnt]={en[x],st[y],qcnt,mcnt,lc};
			}
		}
	}
	sort(q+1,q+qcnt+1,cmp);
	int l=1,r=0,tt=0;
	for(int i=1;i<=qcnt;i++){
		while(l>q[i].l) lcd(dfn[--l]);
		while(r<q[i].r) lcd(dfn[++r]);
		while(l<q[i].l) lcd(dfn[l++]);
		while(r>q[i].r) lcd(dfn[r--]);
		while(tt<q[i].time) modif(++tt);
		while(tt>q[i].time) modif(tt--);
		if(q[i].lca) lcd(q[i].lca);
		ans[q[i].id]=temp;
		if(q[i].lca) lcd(q[i].lca);
	}
	for(int i=1;i<=qcnt;i++) cout<<ans[i]<<'\n';
	
	return 0;
}

莫队加值域分块

例题1Luogu P4867

题意

给定长为 n 的序列,n<=1e5,a[i]∈[1,n] ,给出 m 个询问(m<=1e6),每次查询一段区间内值在a~b中的数的种数

比如对于序列 5 2 3 4 1 ,询问 { l = 2 , r = 4 , a = 1 , b = 3 } ,答案为 2 (区间2~4内权值在1~3的有2和3两种)

解决

如果没有 a、b 的限制,那么是一道简单的板子莫队,那么加上权值的限制后,我们要怎样操作?

1、考虑继续用莫队实现:

对于限制{a,b} ,想想权值类的数据结构优化。莫队本身带有 $n^{3/2}$ 的复杂度,已经是上限了,所以这个维护只能是常数级别(显然没有)或能跟莫队完美结合。

我们发现莫队是有分块操作的,那我们自然想到值域分块

2、值域分块:

顾名思义,这里按值域将1~n分成块长为根号n的块。设数组 sum[i]表示第 i 块中现有不同数字的种类。我们可以在莫队的 add 和 del 操作中轻易维护处 sum 数组

3、怎样记录答案?

显然不能简单的以莫队里l~r中数字种类个数作为答案。对于{a,b},单独处理 a、b所在的块(直接枚举 莫队cnt 数组),中间的累加 sum[i] ,就能得到答案。

显然时间复杂度为根号级别,完成!

代码及解释

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10,M=1e3+10;
int n,m,bs;
int a[N],b[N],cnt[N],sum[M],ans[N],bl[M],br[M];
struct que{
	int id,l,r,a,b;
}q[N];
bool cmp(que x,que y){
	if(b[x.l]!=b[y.l]) return x.l<y.l;
	if(b[x.l]&1) return x.r<y.r;
	return x.r>y.r;
}
void add(int x){
	x=a[x];
	cnt[x]++;
	if(cnt[x]==1) sum[b[x]]++;
}
void del(int x){
	x=a[x];
	cnt[x]--;
	if(cnt[x]==0) sum[b[x]]--;
}
void quer(int i){
	int l=q[i].a,r=q[i].b,id=q[i].id;
	for(int i=l;i<=r&&i<=br[b[l]];i++) if(cnt[i]) ans[id]++;//枚举a所在块
	if(b[l]!=b[r]) for(int i=bl[b[r]];i<=r;i++) if(cnt[i])ans[id]++; //枚举b所在块
	for(int i=b[l]+1;i<b[r];i++) ans[id]+=sum[i];//中间的剩余部分
//复杂度根号n
}

signed main(){
	cin>>n>>m;
	bs=sqrt(n);
	for(int i=1;i<=n;i++) {
		cin>>a[i];
		b[i]=(i-1)/bs+1;//值域分块和莫队的块是一样的
	}
	for(int i=1;i<=b[n];i++) {
		bl[i]=(i-1)*bs+1,br[i]=i*bs;//需要记录每个块的左右端点
	}
	for(int i=1;i<=m;i++) {
		cin>>q[i].l>>q[i].r>>q[i].a>>q[i].b;
		q[i].id=i;
	}
	br[b[n]]=n;
	sort(q+1,q+m+1,cmp);
	int l=1,r=0;
	for(int i=1;i<=m;i++) {
		int L=q[i].l,R=q[i].r;
		while(l>L) add(--l);
		while(r<R) add(++r);
		while(l<L) del(l++);
		while(r>R) del(r--);
		quer(i);//算答案
	}
	for(int i=1;i<=m;i++) 
		cout<<ans[i]<<endl;
	
	return 0;
}

练习 Luogu曼哈顿交易

tips:这题千万注意审题!!!

完结撒花❀❀❀

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值