重修 分块和莫队
带修莫队
相信我会了,这里只考虑时间复杂度。
设我们块大小(\(l,r\) 两轴块大小)为 \(B\),则时间复杂度为 \(O\) 里面:
上柿为三维长方体 \(x\in[1,n],y\in[1,n],z\in[1,m]\) 内 \(m\) 个点以 \(x,y\) 轴分别以 \(B\) 块长分块后用一根每一段与某坐标轴平行的折线串起来,线以分块策略下的最劣情况长度。
(由于我不会画 3D 的图,所以自行脑补 qwq)
我们若规定 \(m>>B\),则上述柿子变为
均衡一下得到 \(B=n^{2/3}\),总复杂度为 \(n^{2/3}m\)。
为啥别的题解得到的 \(B\) 和我的不一样捏。
回滚莫队
分成两种:只增加、只删除。
只删除的例题:P8078 [WC2022] 秃子酋长
二次离线莫队
设莫队单次修改的时间为 \(t\),则二次离线莫队将莫队从 \(O(nt\sqrt{n})\) 优化到 \(O(nt+n\sqrt{n})\),当然前提是满足区间可减性。
给你一个序列 \(a\),每次查询给一个区间 \([l,r]\),查询 \(l \leq i< j \leq r\),且 \(popcnt(a_i \oplus a_j)=k\) 的二元组 \((i,j)\) 的个数.\(\oplus\) 是指按位异或。\(n,q\le 10^5,0\le a_i,k<2^{14}\)。
设 \(m\) 为 \([0,V)\) 中 \(popcnt=k\) 的个数,我们预处理这些数,最后时间复杂度也和 \(m\) 有关。
设 \(f(x,l,r)\) 表示
的 \(y\) 的个数。
由于区间可减,得到
所以下文用 \(f(x,y)\) 来简写 \(f(x,1,y)\)。
只有当 \(k=0\) 时 \(popcnt(a_x\oplus a_x)=k\),所以得到
我们先正常莫队(甚至可以奇偶优化)。举个例子(莫队区间设为 \([L,R]\))。
当 \(L\) 增大至 \(l\) 时,此询问比上次减少
\(L\) 增大、\(R\) 减小、\(R\) 增大 同理,不再赘述。
我们可以用桶 \(O(nm)\) 预处理出 \(p(x)\)。所以剩下的只要拎出 \(\sum_{x=L}^{l-1} f(x,R)\) 类的东西求即可,最后再代回来算答案。
接下来就是二次离线部分了。
我们将每一个形如 \(\sum_{x=l}^{r} f(x,i)\) 的询问挂在 \(i\) 位置上。
然后再用桶扫,同时计算每个位置上的询问。
这部分是 \(O(nm+n\sqrt{n})\),\(n\sqrt{n}\) 是因为二级询问有 \(O(n\sqrt{n})\) 个,每个只要 \(O(1)\) 回答。
然后就做完了,总时间 \(O(nm+n\sqrt{n})\),与开头说的相同。
例题代码:
点击查看代码
//Said no more counting dollars. We'll be counting stars.
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define mem(x,y) memset(x,y,sizeof(x))
#define For(i,j,k) for(int i=j;i<=k;i++)
#define Rof(i,j,k) for(int i=j;i>=k;i--)
#define int long long
#define N 100010
#define V 16384//值域 
vector<int> bu;//0~V-1 popcnt=k 的数 
int n,m,k,a[N],b[N],gap;
struct Que{
	int l,r,id,ans;//ans:比莫队中上次询问答案的增量 
	friend bool operator<(Que x,Que y){return b[x.l]==b[y.l]?((b[x.l]&1)?x.r<y.r:x.r>y.r):x.l<y.l;}
}q[N];
int p[N];//ct((i+1)->[1,i]) ct表示贡献 
int t[V];//桶 
int ans[N];//最终答案 
vector<tuple<int,int,int,int> > v[N];
//第一次莫队留下的询问v[i].<l,r,id,val>:[l,r] 区间中所有 x,sum ct(x->[1,i]),乘系数 val 贡献给询问 i 
signed main(){
	scanf("%lld%lld%lld",&n,&m,&k);
	For(i,0,V-1) if(__builtin_popcount(i)==k) bu.pb(i);
	For(i,1,n) scanf("%lld",a+i); 
	For(i,1,m) scanf("%lld%lld",&q[i].l,&q[i].r),q[i].id=i;
	gap=sqrt(n);
	For(i,1,n) b[i]=(i-1)/gap+1;
	sort(q+1,q+1+m); 
	For(i,1,n){
		for(int j:bu) t[a[i]^j]++;
		p[i]=t[a[i+1]];
	}
	int L=1,R=0,l,r;
	For(i,1,m){
		l=q[i].l,r=q[i].r;
        if(L<l)    v[R].pb(L,l-1,i,-1);
        while(L<l){q[i].ans+=p[L-1]+(!k);++L;} 
        
        if(R>r)    v[L-1].pb(r+1,R,i,1);
        while(R>r){q[i].ans-=p[R-1];     --R;}
        
        if(L>l)    v[R].pb(l,L-1,i,1);
        while(L>l){q[i].ans-=p[L-2]+(!k);--L;}
        
        if(R<r)    v[L-1].pb(R+1,r,i,-1);
        while(R<r){q[i].ans+=p[R];       ++R;}
	}
	mem(t,0);
	int id,val,tmp;
	For(i,1,n){
		for(int j:bu) t[a[i]^j]++;
		for(auto x:v[i]){
			tie(l,r,id,val)=x;
			For(j,l,r){
				tmp=t[a[j]];
				q[id].ans+=tmp*val; 
			} 
		}
	}
	For(i,1,m) q[i].ans+=q[i-1].ans;//累计 
	For(i,1,m) ans[q[i].id]=q[i].ans;//重排 
	For(i,1,m) printf("%lld\n",ans[i]);
return 0;}
树上莫队(fake)
指的是平摊成欧拉序后在序列上做莫队。
树上莫队(real)
首先先得会树分块。
为了保证最终莫队复杂度的正确性,我们需要做到:
- 
属于同一块的节点之间的距离不大。
 - 
每个块中的节点不能太多也不能太少。
 - 
每个节点都要属于一个块。
 - 
编号相邻的块之间的距离不能太大。
 
我们让树分块后依次顺序编号就正好满足了第四个条件了。
分块后的排序方法:若路径 \((u,v)\) 的 \(u\) 的时间戳大于 \(v\) 那么交换 \(u,v\)。然后按照 \(u\) 所在块为第一关键字,\(v\) 的时间戳为第二关键字排序。
注意这里有一个大坑点:
在指针移动的过程中,我们肯定是让移动前的位置和移动后的位置一起向 lca 靠近。然后利用 \(vis\) 标记来
判断这个点是要进入区间还是出区间。
但是这个移动中会出现一个问题,移动时候如果跨过 lca 了会出问题,如下图。
我们按照上面步骤从 \((u,v)\) 移到 \((u',v')\)的时候,两个 lca 都被标记了两次,也就是标记状态没有改变,这是错误的。
所以我们要把 lca 放到最后特判,单独更新。就解决问题了。
形象一点就是:因为若是边权这样处理没有问题,所以我们将树上路径点权移到它连向祖先的边上(这时候要删掉 lca 的点权),成功改为边权,然后正常移动,然后再变回点权(要把 lca 点权加回来)。
这甚至是树上带修莫队太毒瘤了。
代码被我吃了。由于是缝合题,代码有点长,但是很好理解。
点击查看代码
/*
* Author: ShaoJia
* Create Time:        2022-08-24 19:46:10
* Last Modified time: 2022-08-25 14:49:42
* Motto: We'll be counting stars.
*/
#include<bits/stdc++.h>
using namespace std;
#define fir first
#define sec second
#define mkp make_pair
#define pb emplace_back
#define For(i,j,k) for(int i=(j),i##_=(k);i<=i##_;i++)
#define Rof(i,j,k) for(int i=(j),i##_=(k);i>=i##_;i--)
#define ckmx(a,b) a=max(a,b)
#define ckmn(a,b) a=min(a,b)
#define debug(...) cerr<<"#"<<__LINE__<<": "<<__VA_ARGS__<<endl
#define int long long
char buf[1<<21],*p1,*p2;
#define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
inline int read() {
	int x=0,f=1;
	char c=gc();
	while(c<'0'||c>'9'){if(c=='-')f=-1;c=gc();}
	while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=gc();}
	return x*f;
}
//------------------------------------------
const int N=100005,gap=2000,C=16;
int b[N];
struct Que{
	int u,v,t,id;
	friend bool operator<(Que x,Que y){
		if(b[x.u]!=b[y.u]) return b[x.u]<b[y.u];
		if(b[x.v]!=b[y.v]) return b[x.v]<b[y.v];
		return x.t<y.t;
	}
}q[N];
struct Chan{ int x,lst,nxt; }c[N];
vector<int> e[N];
int n,m,qt=0,v[N],w[N],a[N],s[N],st=0;
int f[N][C+1],dep[N],bl=0,tim=0,now,ans[N],vis[N],cnt[N];
void dfs(int rt,int fa){
	dep[rt]=dep[fa]+1;
	f[rt][0]=fa;
	For(i,1,C) f[rt][i]=f[f[rt][i-1]][i-1];
	int tmp=st;
	s[++st]=rt;
	for(int i:e[rt]) if(i!=fa){
		dfs(i,rt);
		if(st-tmp>gap){
			bl++;
			while(st!=tmp) b[s[st--]]=bl;
		}
	}
}
int lca(int x,int y){
	int xx,yy;
	if(dep[x]<dep[y]) swap(x,y);
	Rof(i,C,0){
		xx=f[x][i];
		if(dep[xx]>=dep[y]) x=xx;
	}
	if(x==y) return x;
	Rof(i,C,0){
		xx=f[x][i];
		yy=f[y][i];
		if(xx!=yy) x=xx,y=yy;
	}
	return f[x][0];
}
void del(int x){ now-=w[cnt[x]--]*v[x]; }
void add(int x){ now+=w[++cnt[x]]*v[x]; }
void work(int x){
	if(vis[x]) del(a[x]); else add(a[x]);
	vis[x]^=1;
}
void change(int x,int val){
	if(vis[x]) del(a[x]),add(val);
	a[x]=val;
}
void mov(int x,int y){
	if(dep[x]<dep[y]) swap(x,y);
	while(dep[x]>dep[y])
		work(x),x=f[x][0];
	while(x!=y){
		work(x),x=f[x][0],
		work(y),y=f[y][0];
	}
}
signed main(){
	int opt,x,y,tmp;
	n=read(),m=read(),tmp=read();
	For(i,1,m) v[i]=read();
	For(i,1,n) w[i]=read();
	For(i,1,n-1){
		x=read(),y=read();
		e[x].pb(y);
		e[y].pb(x);
	}
	For(i,1,n) s[i]=a[i]=read();
	while(tmp--){
		opt=read(),x=read(),y=read();
		if(!opt) c[++tim]=(Chan){x,s[x],y},s[x]=y;
		else qt++,q[qt]=(Que){x,y,tim,qt};
	}
	dfs(1,0);
	if(st){ bl++; while(st) b[s[st--]]=bl; }
	sort(q+1,q+1+qt);
	int T=0,U=1,V=1;
	work(1);
	For(i,1,qt){
		while(T<q[i].t) T++,change(c[T].x,c[T].nxt);
		while(T>q[i].t) change(c[T].x,c[T].lst),T--;
		work(lca(U,V));
		mov(U,q[i].u);
		mov(V,q[i].v);
		work(lca(U=q[i].u,V=q[i].v));
		ans[q[i].id]=now;
	}
	For(i,1,qt) printf("%lld\n",ans[i]);
return 0;}
树上撒点
作者:ShaoJia,欢迎分享本文,转载时敬请注明原文来源链接。

                
            
        
浙公网安备 33010602011771号