加载中...

莫队专题

普通莫队

pZ5eyb8.png

SP3267 (模板)

普通莫队模板题,静态多次查询区间内所有数字出现的种类数。

#include <bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#define fr first
#define se second
#define endl '\n'
#define pb push_back
//#define int long long 
typedef long long ll;
typedef __int128 LL;
typedef unsigned long long ull;
typedef pair<int,int> pii;
typedef pair<ll,int> pli;

const int maxn = 30005;
const int maxv = 1000005;
const int maxb = 170;

int n, q;
int a[maxn];
int cnt[maxv], kind=0;

int bi[maxn], bl[maxb], br[maxb];

struct Queries{
	int l, r, id;
}qry[200005];
int ans[200005];

// 普通莫队的排序方式:先按左端点所在块号升序排序,再按右端点数值大小升序排序。
bool cmp(Queries a, Queries b){
	if(bi[a.l] == bi[b.l]){
		if(bi[a.l] & 1){
			return a.r < b.r;
		}
		else{
			return a.r > b.r;
		}
	}
	else{
		return bi[a.l] < bi[b.l];
	}
}

void del(int x){
	if(--cnt[x] == 0){
		kind --;
	}
}
void add(int x){
	if(++cnt[x] == 1){
		kind ++;
	}
}


void solve()
{
	cin >> n;
	for(int i = 1; i <= n; i ++){
		cin >> a[i];
	}

	const int blen = sqrt(n);
	const int bcnt = (n + blen - 1) / blen;
	for(int i = 1; i <= n; i ++){
		bi[i] = (i - 1) / blen + 1;
	}
	for(int i = 1; i <= bcnt; i ++){
		bl[i] = (i - 1) * blen + 1;
		br[i] = min(n, i * blen);
	}

	cin >> q;
	for(int i = 1; i <= q; i ++){
		int l, r;
		cin >> l >> r;
		qry[i] = {l, r, i};
	}
	sort(qry + 1, qry + q + 1, cmp);

	int winl = 1, winr = 0; // 初始时滑动窗口内为空
	for(auto &[l, r, id] : qry){
		while(winl > l){
			add(a[--winl]);
		}
		while(winr < r){
			add(a[++winr]);
		}
		while(winl < l){
			del(a[winl++]);
		}
		while(winr > r){
			del(a[winr--]);
		}
		ans[id] = kind;
	}
	for(int i = 1; i <= q; i ++){
		cout << ans[i] << "\n";
	}
}


signed main()
{
//  freopen("in.txt", "rt", stdin);
//  freopen("out.txt", "wt", stdout);
	ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
	// int T=1; cin>>T; while(T--)
	solve();
	return 0;
}

P3709

莫队解决 多查询区间 \([l,r]\) 内众数的出现次数。

由于每次修改对 cnt 的变化量只会是 +1,-1,因此区间众数出现次数是很容易动态维护的。

code

P4462

多查询区间 \([l,r]\) 中的异或和等于 \(k\)(常数,每次查询都一样)的子数组个数。

莫队维护 \(a\) 的前缀异或和 \(pre\) 即可。注意对于查询 \([l \backsim r]\) 需要维护的是 \(pre[l-1 \backsim r]\)。具体细节见 code。

code

带修莫队

pZ5uyb6.png

pZ5uWPe.png

对于每条查询,除了区间左右端点 \([l, r]\) 外,还需要维护一个关于修改的时间点 \(t\),表示在当前查询之前一共进行了多少次修改操作(执行的也就是在原查询序列中的前 \(t\) 条修改)。

同时我们也需要额外维护一个关于修改时间点的窗口,该窗口只有一个指针,前缀所有修改已经执行,后缀所有修改均未执行。对于每一条查询,我们需要前进 / 回退到对于该查询的修改时间点上,并对原序列和当前区间内的词频统计做相应修改 / 恢复。

P1903 (模板)

在多次查询区间 \([l,r]\) 内所有数字出现的种类数 的基础上,添加单点修改操作。

#include <bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#define fr first
#define se second
#define endl '\n'
#define pb push_back
//#define int long long 
typedef long long ll;
typedef __int128 LL;
typedef unsigned long long ull;
typedef pair<int,int> pii;
typedef pair<ll,int> pli;

const int maxn = 133336;
const int maxv = 1000005;
const int maxb = 400;

int n, q, tot_q;
int a[maxn];
int cnt[maxv], kind=0;

int bi[maxn], bl[maxb], br[maxb];

struct Queries{
	int l, r, t, id;
}qry[maxn];
int ans[maxn];

int upd_tim=0;
pii upd_seq[maxn];

// 带修莫队的排序方式:先按左端点所在块号升序排序,再按右端点所在块号升序排序,最后按照修改时间点升序排序。
bool cmp(Queries a, Queries b){
	if(bi[a.l] == bi[b.l] && bi[a.r] == bi[b.r]){
		return a.t < b.t;
	}
	if(bi[a.l] == bi[b.l]){
		return bi[a.r] < bi[b.r];
	}
	return bi[a.l] < bi[b.l];
}

void del(int x){
	if(--cnt[x] == 0){
		kind --;
	}
}
void add(int x){
	if(++cnt[x] == 1){
		kind ++;
	}
}
// 修改时间点移动函数
void moveTime(int ql, int qr, int tim){
	auto [pos, val] = upd_seq[tim];
	if(ql <= pos && pos <= qr){
		del(a[pos]);
		add(val);
	}
	swap(upd_seq[tim].second, a[pos]);
}

void solve()
{
	cin >> n >> q;
	for(int i = 1; i <= n; i ++){
		cin >> a[i];
	}

	const int blen = max(1, (int)pow(n, 2.0/3)); // 注意带修莫队的块长不再是根号,而是 2/3!!!
	const int bcnt = (n + blen - 1) / blen;
	for(int i = 1; i <= n; i ++){
		bi[i] = (i - 1) / blen + 1;
	}
	for(int i = 1; i <= bcnt; i ++){
		bl[i] = (i - 1) * blen + 1;
		br[i] = min(n, i * blen);
	}

	for(int i = 1; i <= q; i ++){
		char op; cin >> op;
		if(op == 'Q'){
			int l, r;
			cin >> l >> r;
			qry[++tot_q] = {l, r, upd_tim, tot_q};
		}
		else{
			int pos, v;
			cin >> pos >> v;
			upd_seq[++upd_tim] = {pos, v};
		}
	}
	sort(qry + 1, qry + tot_q + 1, cmp);

	int winl = 1, winr = 0, wint = 0; // 初始时两个滑动窗口内均为空
	for(auto &[l, r, t, id] : qry){
		while(winl > l){
			add(a[--winl]);
		}
		while(winr < r){
			add(a[++winr]);
		}
		while(winl < l){
			del(a[winl++]);
		}
		while(winr > r){
			del(a[winr--]);
		}
		while(wint < t){
			moveTime(l, r, ++wint);
		}
		while(wint > t){
			moveTime(l, r, wint--);
		}
		ans[id] = kind;
	}
	for(int i = 1; i <= tot_q; i ++){
		cout << ans[i] << "\n";
	}
}


signed main()
{
//  freopen("in.txt", "rt", stdin);
//  freopen("out.txt", "wt", stdout);
	ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
	// int T=1; cin>>T; while(T--)
	solve();
	return 0;
}

CF940F

Q:带有单点修改,多次查询给定区间 \([l, r]\),求对于当前序列的该子区间内 \(cnt_{0 \backsim 10^{9}}\) 的 mex,其中 \(cnt_{v}\) 表示 \(v\)\([l,r]\) 内的出现次数。

类似上面 P3709,维护一个 \(cntt\) 数组,表示 \(cnt\) 的桶。用带修莫队实时维护每个查询区间的 \(cnt\)\(cntt\)

显然 \(mex\) 至少为 \(1\);而答案不会超过 \(O(\sqrt n)\)(很显然的根号性质),因此对于每个询问只需要通过暴力遍历 \(cntt\) 的方式找到答案。具体实现见 code。

code

回滚莫队

分成 只增回滚莫队 与 只删回滚莫队 两种。

只增回滚莫队

pZ5QzMd.png

P14420 (模板)

pZ5QrKs.png

pZ5Q25T.png

#include <bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#define fr first
#define se second
#define endl '\n'
#define pb push_back
//#define int long long 
typedef long long ll;
typedef __int128 LL;
typedef unsigned long long ull;
typedef pair<int,int> pii;
typedef pair<ll,int> pli;

const int maxn = 100005;
const int maxv = 100005;
const int maxb = 370;

int n, q, M;
int a[maxn], ori[maxn];
int cnt[maxv];

int bi[maxn], bl[maxb], br[maxb];
ll curAns=0;
ll backup;

struct Queries{
	int l, r, id;
}qry[100005];
ll ans[100005];

// 只增回滚莫队的排序方式:先按左端点所在块号升序排序,再按右端点数值大小升序排序。(与普通莫队相同)
bool cmp(Queries a, Queries b){
	if(bi[a.l] == bi[b.l]){
		return a.r < b.r;
	}
	return bi[a.l] < bi[b.l];
}

// 由于是只增回滚莫队,当窗口缩减时不更新答案,只在窗口扩大时更新答案
void del(int x){ 
	cnt[x] --;
}
void add(int x){
	cnt[x] ++;
	curAns = max(curAns, 1ll * ori[x] * cnt[x]);
}

// 暴力解决某个块内的区间的答案
ll bruteforce(int l, int r){
	ll res = 0;
	for(int i = l; i <= r; i ++){
		cnt[a[i]] ++;
		res = max(res, 1ll * ori[a[i]] * cnt[a[i]]);
	}
	for(int i = l; i <= r; i ++){
		cnt[a[i]] --;
	}
	return res;
}

void solve()
{
	cin >> n >> q;
	vector<int> alls;
	for(int i = 1; i <= n; i ++){
		cin >> a[i];
		alls.pb(a[i]);
	}
	sort(alls.begin(), alls.end());
	alls.erase(unique(alls.begin(), alls.end()), alls.end());
	M = alls.size();
	map<int,int> mp;
	for(int i = 0; i < M; i ++){
		mp[alls[i]] = i + 1;
		ori[i + 1] = alls[i];
	}
	for(int i = 1; i <= n; i ++) a[i] = mp[a[i]];

	const int blen = sqrt(n);
	const int bcnt = (n + blen - 1) / blen;
	for(int i = 1; i <= n; i ++){
		bi[i] = (i - 1) / blen + 1;
	}
	for(int i = 1; i <= bcnt; i ++){
		bl[i] = (i - 1) * blen + 1;
		br[i] = min(n, i * blen);
	}

	for(int i = 1; i <= q; i ++){
		int l, r;
		cin >> l >> r;
		qry[i] = {l, r, i};
	}
	sort(qry + 1, qry + q + 1, cmp);

	// 按照块的顺序遍历,依次解决每个查询
	for(int block = 1, qi = 1; block <= bcnt && qi <= q; block ++){
		curAns = 0;
		for(int i = 1; i <= M; i ++) cnt[i] = 0;
		int winl = br[block] + 1, winr = br[block]; // 初始化滑动窗口
		while(qi <= q && bi[qry[qi].l] == block){ // 每一轮解决当前块内的所有查询
			auto [l, r, id] = qry[qi];
			if(r <= br[block]){ // 区间在整个块内,直接暴力
				ans[id] = bruteforce(l, r);
			}
			else{
				while(winr < r){ // 先扩窗口右端点
					add(a[++winr]);
				}
				// !!当右端点扩展完成时,备份下来此时的答案,等到左端点扩展完后回滚时恢复 curAns
				backup = curAns; // 备份,用于回滚
				while(winl > l){ // 再扩窗口左端点
					add(a[--winl]);
				}
				ans[id] = curAns;
				// !! 回滚
				curAns = backup; 
				while(winl <= br[block]){
					del(a[winl++]);
				}
			}
			qi ++;
		}
	}

	for(int i = 1; i <= q; i ++){
		cout << ans[i] << "\n";
	}
}


signed main()
{
//  freopen("in.txt", "rt", stdin);
//  freopen("out.txt", "wt", stdout);
	ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
	// int T=1; cin>>T; while(T--)
	solve();
	return 0;
}

多次查询区间最小众数

一个经典问题,可以利用只增回滚莫队解决。

假设我们维护好了某个集合的最小众数 minmode 与它出现的次数 maxcnt,以及每个数的词频。那么显然,往这个集合内添加一个数,minmode 和 maxcnt 这两个信息是很容易实时更新的;而若从这个集合中删除一个数,这两个信息便很难维护好。

思路与上一道模板题是一样的,这里就只贴代码了,偷个懒qwq。。。

#include <bits/stdc++.h>

using namespace std;

struct Query {
   int l, r, k, id;
};

const int MAXN = 10001;
const int MAXM = 50001;
const int MAXB = 301;

int n, m;
int arr[MAXN];
Query query[MAXM];
int sorted[MAXN];
int cntv;

int blen, bnum;
int bi[MAXN];
int br[MAXB];

int cnt[MAXN];
int maxCnt;
int minMode;

int ans[MAXM];

bool QueryCmp(Query &a, Query &b) {
   if (bi[a.l] != bi[b.l]) {
       return bi[a.l] < bi[b.l];
   }
   return a.r < b.r;
}

int kth(int num) {
   int left = 1, right = cntv, ret = 0;
   while (left <= right) {
       int mid = (left + right) >> 1;
       if (sorted[mid] <= num) {
           ret = mid;
           left = mid + 1;
       } else {
           right = mid - 1;
       }
   }
   return ret;
}

int force(int l, int r, int k) {
   int mx = 0, who = 0;
   for (int i = l; i <= r; i++) {
       cnt[arr[i]]++;
   }
   for (int i = l; i <= r; i++) {
       int num = arr[i];
       if (cnt[num] > mx || (cnt[num] == mx && num < who)) {
           mx = cnt[num];
           who = num;
       }
   }
   for (int i = l; i <= r; i++) {
       cnt[arr[i]]--;
   }
   return mx >= k ? sorted[who] : -1;
}

void add(int num) {
   cnt[num]++;
   if (cnt[num] > maxCnt || (cnt[num] == maxCnt && num < minMode)) {
       maxCnt = cnt[num];
       minMode = num;
   }
}

void del(int num) {
   cnt[num]--;
}

void compute() {
   for (int block = 1, qi = 1; block <= bnum && qi <= m; block++) {
       maxCnt = 0;
       minMode = 0;
       fill(cnt + 1, cnt + cntv + 1, 0);
       int winl = br[block] + 1, winr = br[block];
       for (; qi <= m && bi[query[qi].l] == block; qi++) {
           int jobl = query[qi].l;
           int jobr = query[qi].r;
           int jobk = query[qi].k;
           int id = query[qi].id;
           if (jobr <= br[block]) {
               ans[id] = force(jobl, jobr, jobk);
           } else {
               while (winr < jobr) {
                   add(arr[++winr]);
               }
               int backupCnt = maxCnt;
               int backupNum = minMode;
               while (winl > jobl) {
                   add(arr[--winl]);
               }
               if (maxCnt >= jobk) {
                   ans[id] = sorted[minMode];
               } else {
               	ans[id] = -1;
               }
               maxCnt = backupCnt;
               minMode = backupNum;
               while (winl <= br[block]) {
                   del(arr[winl++]);
               }
           }
       }
   }
}

void prepare() {
   for (int i = 1; i <= n; i++) {
       sorted[i] = arr[i];
   }
   sort(sorted + 1, sorted + n + 1);
   cntv = 1;
   for (int i = 2; i <= n; i++) {
       if (sorted[cntv] != sorted[i]) {
           sorted[++cntv] = sorted[i];
       }
   }
   for (int i = 1; i <= n; i++) {
       arr[i] = kth(arr[i]);
   }
   blen = (int)sqrt(n);
   bnum = (n + blen - 1) / blen;
   for (int i = 1; i <= n; i++) {
       bi[i] = (i - 1) / blen + 1;
   }
   for (int i = 1; i <= bnum; i++) {
       br[i] = min(i * blen, n);
   }
   sort(query + 1, query + m + 1, QueryCmp);
}

class Solution {
public:
   vector<int> subarrayMajority(vector<int>& nums, vector<vector<int>>& queries) {
       n = (int)nums.size();
       m = (int)queries.size();
       for (int i = 1; i <= n; i++) {
           arr[i] = nums[i - 1];
       }
       for (int i = 1; i <= m; i++) {
           query[i].l = queries[i - 1][0] + 1;
           query[i].r = queries[i - 1][1] + 1;
           query[i].k = queries[i - 1][2];
           query[i].id = i;
       }
       prepare();
       compute();
       vector<int> ret(m);
       for (int i = 1; i <= m; i++) {
           ret[i - 1] = ans[i];
       }
       return ret;
   }
};

P5906

Q:多次查询 \(a[l, r]\) 中相隔最远的两个相同数字的距离。

需要维护的信息:

  • \(mostRight[x]\):数字 \(x\) 在当前区间内最右侧出现的位置。
  • \(maxDist\):当前区间的答案
  • \(First[x]\):在当前区间的左端点所在块右侧的整个区间中,数字 \(x\) 最左侧出现的位置。

具体实现见 code。

code

只删回滚莫队

pZ5sceP.png

P4137 (模板)

Q:静态多次查询区间 mex。

显然 mex 这个信息是适合只有删除操作的时候实时维护的。考虑对于某个可重集 \(S\)\(mex = v\)。若往集合中增加一个数,那么 \(mex\) 的更新还需要依靠其他更大的数的信息,不易实时维护;而若在集合中删除一个数,那么只要这个数在集合中被删没了,新的 \(mex\) 自然应该是原 \(mex\) 与这个数的最小值,否则 \(mex\) 不变。

#include <bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#define fr first
#define se second
#define endl '\n'
#define pb push_back
//#define int long long 
typedef long long ll;
typedef __int128 LL;
typedef unsigned long long ull;
typedef pair<int,int> pii;
typedef pair<ll,int> pli;

const int maxn = 200005;
const int maxv = 200005;
const int maxb = 460;

int n, q, M;
int a[maxn], ori[maxn];
int cnt[maxv];

int bi[maxn], bl[maxb], br[maxb];

int mex;
int backup;

struct Queries{
	int l, r, id;
}qry[200005];
int ans[200005];

// 只删回滚莫队的排序方式:先按左端点所在块号升序排序,再按右端点数值大小降序排序。
bool cmp(Queries a, Queries b){
	if(bi[a.l] == bi[b.l]){
		return a.r > b.r;
	}
	return bi[a.l] < bi[b.l];
}

// 对于只删回滚莫队:只有 del 更新 mex 的信息,而 add 不需要(后续由 backup 恢复 mex) 
void del(int x){
	if(--cnt[x] == 0){
		mex = min(mex, x);
	}
}
void add(int x){
	cnt[x] ++;
}

void solve()
{
	cin >> n >> q;
	for(int i = 1; i <= n; i ++){
		cin >> a[i];
	}
	
	const int blen = sqrt(n);
	const int bcnt = (n + blen - 1) / blen;
	for(int i = 1; i <= n; i ++){
		bi[i] = (i - 1) / blen + 1;
	}
	for(int i = 1; i <= bcnt; i ++){
		bl[i] = (i - 1) * blen + 1;
		br[i] = min(n, i * blen);
	}

	for(int i = 1; i <= q; i ++){
		int l, r;
		cin >> l >> r;
		qry[i] = {l, r, i};
	}
	sort(qry + 1, qry + q + 1, cmp);

	// 先处理好整个区间
	for(int i = 1; i <= n; i ++){
		cnt[a[i]] ++;
	}
	mex = 0;
	while(cnt[mex]) mex ++;

	int winl = 1, winr = n; // 初始时窗口是整个序列
	// 按照块的顺序遍历,依次解决每个查询
	for(int block = 1, qi = 1; block <= bcnt && qi <= q; block ++){
		while(winl < bl[block]){ // 先将窗口左端点更新到当前查询区间左端点所在块的左边界
			del(a[winl++]);
		}
		int before = mex; // !!需要先备份下来整体的 mex,以便后续恢复
		while(qi <= q && bi[qry[qi].l] == block){
			auto [l, r, id] = qry[qi];
			while(winr > r){
				del(a[winr--]);
			}
			backup = mex; // 备份
			while(winl < l){
				del(a[winl++]);
			}
			ans[id] = mex;
			mex = backup;
			while(winl > bl[block]){
				add(a[--winl]);
			}
			qi ++;	
		}
		// !!每解决完一个块中的查询,需要将窗口右边界恢复到 n
		while(winr < n){
			add(a[++winr]);
		}
		// !!mex 信息恢复,与 backup 原理一致
		mex = before;
	}

	for(int i = 1; i <= q; i ++){
		cout << ans[i] << "\n";
	}
}


signed main()
{
//  freopen("in.txt", "rt", stdin);
//  freopen("out.txt", "wt", stdout);
	ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
	// int T=1; cin>>T; while(T--)
	solve();
	return 0;
}

P8078

先鸽着,以后补~

code

树上莫队

pZIi5ad.png

SP10707 (模板)

多次查询树上路径 \((u, v)\) 中的不同权值种类数。

#include <bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#define fr first
#define se second
#define endl '\n'
#define pb push_back
//#define int long long 
typedef long long ll;
typedef __int128 LL;
typedef unsigned long long ull;
typedef pair<int,int> pii;
typedef pair<ll,int> pli;

const int maxn = 40005;

int n, m;
int a[maxn];
vector<int> G[maxn];
int dep[maxn], fa[maxn][18];
int st[maxn]; // st[u] 表示结点 u 在括号序中的开始结点;
int ed[maxn]; // ed[u] 表示结点 u 在括号序中的结束结点;
int seg[maxn<<1]; // seg[i] 表示括号序为 i 的代表结点
int cntd; // 与dfn中的tim类似,在dfs过程中实时递增
int bi[maxn<<1]; // 对括号序seg作序列分块与莫队操作

// 窗口信息
// 在窗口中,出现一次的数计入,出现两次的数不计,因此用 vis 数组表示每个结点的翻转性
bool vis[maxn]; 
int cnt[maxn];
int kind=0;
int ans[100005];

struct Queries{
	int l, r, lca, id;
}qry[100005];

// 普通莫队排序
bool cmp(Queries a, Queries b){
	if(bi[a.l] == bi[b.l]) return a.r < b.r;
	return bi[a.l] < bi[b.l];
}

void dfs(int u, int father){ // 初始化
	dep[u] = dep[father] + 1;
	fa[u][0] = father;
	// 刚到达u时计入u的开始序
	seg[++cntd] = u;
	st[u] = cntd;
	for(int i = 1; i <= 15; i ++){
		fa[u][i] = fa[fa[u][i - 1]][i - 1];
	}
	for(auto v : G[u]){
		if(v == father) continue;
		dfs(v, u);
	}
	// 回溯时计入u的结束序
	seg[++cntd] = u;
	ed[u] = cntd;
}

int getlca(int u, int v){
	if(dep[u] < dep[v]) swap(u, v);
	for(int i = 15; i >= 0; i --){
		if(dep[fa[u][i]] >= dep[v]){
			u = fa[u][i];
		}
	}
	if(u == v) return v;
	for(int i = 15; i >= 0; i --){
		if(fa[u][i] != fa[v][i]){
			u = fa[u][i];
			v = fa[v][i];
		}
	}
	return fa[u][0];
}

void invert(int u){ // 翻转,处理结点u时根据vis[u]来决定操作的正逆性
	int val = a[u];
	if(vis[u]){ // 删除
		if(--cnt[val] == 0){
			kind --;
		}
	}
	else{
		if(++cnt[val] == 1){
			kind ++;
		}
	}
	vis[u] ^= 1;
}

void solve()
{
	cin >> n >> m;
	vector<int> alls;
	for(int i = 1; i <= n; i ++){
		cin >> a[i];
		alls.pb(a[i]);
	}
	sort(alls.begin(), alls.end());
	alls.erase(unique(alls.begin(), alls.end()), alls.end());
	map<int,int> mp;
	for(int i = 0; i < alls.size(); i ++) mp[alls[i]] = i + 1;
	for(int i = 1; i <= n; i ++) a[i] = mp[a[i]];

	for(int i = 1; i <= n - 1; i ++){
		int u, v;
		cin >> u >> v;
		G[u].pb(v); G[v].pb(u);
	}
	dfs(1, 0);

	const int blen = sqrt(cntd);
	for(int i = 1; i <= n; i ++){
		bi[i] = (i - 1) / blen + 1;
	}

	for(int i = 1; i <= m; i ++){
		int u, v;
		cin >> u >> v;
		if(st[v] < st[u]) swap(u, v);
		int lca = getlca(u, v);
		if(u == lca){
			qry[i] = {st[u], st[v], 0, i};
		}
		else{
			qry[i] = {ed[u], st[v], lca, i};
		}
	}
	sort(qry + 1, qry + 1 + m, cmp);

	int winl = 1, winr = 0;
	for(int i = 1; i <= m; i ++){
		auto [l, r, lca, id] = qry[i];
		while(winl > l){
			invert(seg[--winl]);
		}
		while(winr < r){
			invert(seg[++winr]);
		}
		while(winl < l){
			invert(seg[winl++]);
		}
		while(winr > r){
			invert(seg[winr--]);
		}
		if(lca > 0){
			invert(lca);
		}
		ans[id] = kind;
		if(lca > 0){
			invert(lca);
		}
	}	

	for(int i = 1; i <= m; i ++){
		cout << ans[i] << '\n';
	}
}


signed main()
{
//  freopen("in.txt", "rt", stdin);
//  freopen("out.txt", "wt", stdout);
	ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
	// int T=1; cin>>T; while(T--)
	solve();
	return 0;
}

P4074

树上带修莫队。

依旧以后再补~

code

posted @ 2026-02-03 15:07  小橘奏  阅读(20)  评论(0)    收藏  举报