莫队

简介

分治思想的离线数据结构。适于处理区间内统计元素个数相关的问题。

主要包括:

  • 普通莫队(只有询问)。
  • 带修莫队(修改和询问并存)。
  • 回滚莫队(只增加或只删除)。
  • 树上莫队。
  • 二次离线莫队。

例题

P3901 数列找不同

将以此题为例,剖析莫队的主要思想。

  1. 统计区间内出现次数恰好为一次的元素个数 \(tot\),若 \(tot = R - L + 1\),则合法。
  2. \(Q\) 次询问 \([L, R]\) 看做一个二维平面点对,将 \(x\)\(\to L\)\(y\)\(\to R\)。两次询问间的转移代价恰好是曼哈顿距离,那么 \(Q\) 次询问的总体转移代价理论最小值即为关于曼哈顿路径的最小生成树。
  3. 实际实现中,将 \(Q\) 次操作离线,整体统筹规划,做到相对的优化即可。
  4. 具体而言,将序列分成 \(\sqrt n\) 块,将 \(Q\) 次询问排序。第一关键字为 \(L\) 所在的块的编号,第二关键字是 \(R\) 的大小。
  5. 维护全局指针 \(posl, posr\) 表示当前统计了 \([posl, posr]\) 的信息。
  6. 对于一次询问 \([L, R]\),将 \(posl \to L, posr \to R\),若 \(posl \to posl + 1\),则删除 (\(cnt_{a_x} \to cnt_{a_x} - 1\)),反之添加。\(posr\) 同理。
  7. 时间复杂度分析:
    分析总时间复杂度就是分析指针的移动总次数。
    左指针 \(posl\):设每个块内最大下标为 \(mx_i\),相邻两个块的左端点距离为 \(mx_i - mx_{i - 1}\)。由于排序后的相邻询问一定在相邻块中(若块不相邻则证明中间的块中不存在询问),所以总时间复杂度 \(O(n \sqrt n)\)
    右指针 \(posr\):左端点在同一块时,\(R\) 是从大到小排列的,因此 \(posr\) 单调不减。单块中 \(posr\) 的移动次数为 \(O(n)\)。至多换块 \(O(\sqrt n)\) 块,因此总时间复杂度 \(O(n \sqrt n)\)
  8. 注意事项:
  • 指针 \(posl \to 1\)\(posr \to 0\) 最佳。
  • 若大幅度超时,通常是排序问题。
  • 移动指针时必定先 adddel
  1. 常数优化:奇偶排序。对于右指针,奇数块从小到大排序,偶数块从大到小排序。

代码:

#include <bits/stdc++.h>
#define int long long
using namespace std;

const int N = 1e5 + 5;
int n, Q, l, r, res, a[N], buc[N], ans[N];

namespace Block {
	int kc, id[N];
	struct Node {
		int l, r, id;
	} q[N];
	
	inline bool cmp(Node a, Node b) {
		if(id[a.l] == id[b.l]) return id[a.r] < id[b.r];
		
		return id[a.l] < id[b.l];
	}
	
	inline void init() {
		kc = sqrt(n);
		
		for(int i = 1 ; i <= n ; ++ i)
			 id[i] = (i - 1) / kc + 1;
		
		sort(q + 1, q + 1 + Q, cmp);
		
		return ;
	}
	
	inline void add(int x) {
		if(! buc[a[x]] ++) ++ res;
		
		return ;
	}
	
	inline void del(int x) {
		if(! -- buc[a[x]]) -- res;
		
		return ;
	}
}

using namespace Block;

signed main() {
	ios_base :: sync_with_stdio(NULL);
	cin.tie(nullptr);
	cout.tie(nullptr);
	
	cin >> n >> Q;
	
	for(int i = 1 ; i <= n ; ++ i)
		cin >> a[i];
	
	for(int i = 1 ; i <= Q ; ++ i)
		cin >> l >> r, q[i] = {l, r, i};
	
	init();
	
	int posl = 1, posr = 0;
	
	for(int i = 1 ; i <= Q ; ++ i) {
		l = q[i].l, r = q[i].r;
		
		while(posl > l) add(-- posl);
		while(posr < r) add(++ posr);
		while(posl < l) del(posl ++);
		while(posr > r) del(posr --);
		
		ans[q[i].id] = (res == r - l + 1);
	}
	
	for(int i = 1 ; i <= Q ; ++ i) {
		if(ans[i]) cout << "Yes\n";
		else cout << "No\n";
	}
	
	return 0;
}

P2709 小B的询问

在移动指针时顺便维护平方和的贡献即可。

代码:

#include <bits/stdc++.h>
#define int long long
using namespace std;

const int N = 1e5 + 5;
int n, Q, l, r, k, res, a[N], buc[N], ans[N];

namespace Block {
	int kc, id[N];
	struct Node {
		int l, r, id;
	} q[N];
	
	inline bool cmp(Node a, Node b) {
		if(id[a.l] == id[b.l]) return id[a.r] < id[b.r];
		
		return id[a.l] < id[b.l];
	}
	
	inline void init() {
		kc = sqrt(n);
		
		for(int i = 1 ; i <= n ; ++ i)
			 id[i] = (i - 1) / kc + 1;
		
		sort(q + 1, q + 1 + Q, cmp);
		
		return ;
	}
	
	inline void add(int x) {
		res = res + 2 * buc[a[x]] + 1;
		++ buc[a[x]];
		
		return ;
	}
	
	inline void del(int x) {
		res = res - 2 * buc[a[x]] + 1;
		-- buc[a[x]];
		
		return ;
	}
}

using namespace Block;

signed main() {
	ios_base :: sync_with_stdio(NULL);
	cin.tie(nullptr);
	cout.tie(nullptr);
	
	cin >> n >> Q >> k;
	
	for(int i = 1 ; i <= n ; ++ i)
		cin >> a[i];
	
	for(int i = 1 ; i <= Q ; ++ i)
		cin >> l >> r, q[i] = {l, r, i};
	
	init();
	
	int posl = 1, posr = 0;
	
	for(int i = 1 ; i <= Q ; ++ i) {
		l = q[i].l, r = q[i].r;
		
		while(posl > l) add(-- posl);
		while(posr < r) add(++ posr);
		while(posl < l) del(posl ++);
		while(posr > r) del(posr --);
		
		ans[q[i].id] = res;
	}
	
	for(int i = 1 ; i <= Q ; ++ i)
		cout << ans[i] << '\n';
	
	return 0;
}

P4396 [AHOI2013] 作业

思路:

  1. 值域分块解决 \([a, b]\),莫队 解决 \([L, R]\)
  2. 莫队 \(posl, posr\) 的移动对应着 \(cnt_{val}\) 的加减,进而对应值域中的某个点。
  3. 值域分块,容易进行单点修改和维护值域上 \([i, j]\) 的元素个数和种类数两个信息。

代码:

#include <bits/stdc++.h>
#define int long long
using namespace std;

const int N = 1e5 + 5;
int n, Q, l, r, k, K, a[N], ans1[N], ans2[N];

namespace Block {
  int kc, cnt, L[N], R[N], id[N], val[N], sum[N], apr[N];
  struct Node {
    int l, r, a, b, id;
  } q[N];

  inline bool cmp(Node a, Node b) {
    if(id[a.l] == id[b.l]) {
      if(id[a.l] & 1) return id[a.r] > id[b.r];

      return id[a.r] < id[b.r];
    }

    return id[a.l] < id[b.l];
  }

  inline void init() {
    kc = sqrt(N - 5);
    cnt = n / kc;

    for(int i = 1 ; i <= n ; ++ i)
      id[i] = (i - 1) / kc + 1;
    
    for(int i = 1 ; i <= cnt ; ++ i)
      L[i] = R[i - 1] + 1, R[i] = R[i - 1] + kc;
    
    if(R[cnt] != n) {
      ++ cnt;

      L[cnt] = R[cnt - 1] + 1;
      R[cnt] = n;
    }

    sort(q + 1, q + 1 + Q, cmp);
    
    return ;
  }

  inline void Add(int x, int k) {
    if(! val[x]) ++ apr[id[x]];
    val[x] += k;
    sum[id[x]] += k;
    if(! val[x]) -- apr[id[x]];

    return ;
  }

  inline int query(int l, int r) {
    int res = 0;

    for(int i = l ; i <= min(R[id[l]], r) ; ++ i)
      res += val[i];
    
    if(id[l] != id[r])
      for(int i = L[id[r]] ; i <= r ; ++ i)
        res += val[i];
    
    for(int i = id[l] + 1 ; i <= id[r] - 1 ; ++ i)
      res += sum[i];
    
    return res;
  }

  inline int Query(int l, int r) {
    int res = 0;

    for(int i = l ; i <= min(R[id[l]], r) ; ++ i)
      if(val[i]) ++ res;
    
    if(id[l] != id[r])
      for(int i = L[id[r]] ; i <= r ; ++ i)
        if(val[i]) ++ res;
    
    for(int i = id[l] + 1 ; i <= id[r] - 1 ; ++ i)
      res += apr[i];

    return res;
  }

  inline void add(int x) {
    Add(a[x], 1);

    return ;
  }

  inline void del(int x) {
    Add(a[x], -1);

    return ;
  }
}

using namespace Block;

signed main() {
  ios_base :: sync_with_stdio(NULL);
  cin.tie(nullptr);
  cout.tie(nullptr);

  cin >> n >> Q;
  for(int i = 1 ; i <= n ; ++ i)
    cin >> a[i];
  for(int i = 1 ; i <= Q ; ++ i)
    cin >> l >> r >> k >> K, q[i] = {l, r, k, K, i};

  init();

  int posl = 1, posr = 0;

  for(int i = 1 ; i <= Q ; ++ i) {
    int l = q[i].l, r = q[i].r;

    while(posl > l) add(-- posl);
    while(posr < r) add(++ posr);
    while(posl < l) del(posl ++);
    while(posr > r) del(posr --);

    ans1[q[i].id] = query(q[i].a, q[i].b);
    ans2[q[i].id] = Query(q[i].a, q[i].b);
  }

  for(int i = 1 ; i <= Q ; ++ i)
    cout << ans1[i] << ' ' << ans2[i] << '\n';

  return 0;
}

P5268 [SNOI2017] 一个简单的询问

  1. 推式子可得,一组询问 \((l_1, r_1, l_2, r_2)\) 可以拆成四个前缀询问。
  2. 问题转化为某个值 \(x\) 在两个前缀出现的次数,做乘积。
  3. 两个指针分别维护乘积式子的两项即可。

本题考察的是莫队的本质理解,不仅仅是区间问题,部分静态二维问题也可以考虑莫队。

代码:

#include <bits/stdc++.h>
#define int long long
using namespace std;

const int N = 5e4 + 5;
int n, m, Q, l1, l2, r1, r2, res, a[N], ans[N];

namespace Block {
	int kc, id[N], cnt[N], buc[N];
	struct Node {
		int x, y, id, val;
	} q[N << 2];
	
	inline bool cmp(Node a, Node b) {
		if(id[a.x] == id[b.x]) {
			if(id[a.x] & 1) return a.y > b.y;
			
			return a.y < b.y;
		}
		
		return id[a.x] < id[b.x];
	}
	
	inline void init() {
		kc = sqrt(n);
		
		for(int i = 1 ; i <= n ; ++ i)
			id[i] = (i - 1) / kc + 1;
		
		sort(q + 1, q + 1 + Q, cmp);
		
		return ;
	}
	
	inline void add1(int x) {
		++ cnt[a[x]];
		res += buc[a[x]];
		
		return ;
	}
	
	inline void add2(int x) {
		++ buc[a[x]];
		res += cnt[a[x]];
		
		return ;
	}
	
	inline void del1(int x) {
		-- cnt[a[x]];
		res -= buc[a[x]];
		
		return ;
	}
	
	inline void del2(int x) {
		-- buc[a[x]];
		res -= cnt[a[x]];
		
		return ;
	}
}

using namespace Block;

signed main() {
	ios_base :: sync_with_stdio(NULL);
	cin.tie(nullptr);
	cout.tie(nullptr);
	
	cin >> n;
	for(int i = 1 ; i <= n ; ++ i)
		cin >> a[i];
	cin >> m;
	for(int i = 1 ; i <= m ; ++ i) {
		cin >> l1 >> r1 >> l2 >> r2;
		
		q[++ Q] = {r1, r2, i, 1};
		q[++ Q] = {r1, l2 - 1, i, -1};
		q[++ Q] = {l1 - 1, r2, i, -1};
		q[++ Q] = {l1 - 1, l2 - 1, i, 1};
	}
	
	init();
	
	int posx = 0, posy = 0;
	
	for(int i = 1 ; i <= Q ; ++ i) {
		int x = q[i].x, y = q[i].y;
		
		while(posx < x) add1(++ posx);
		while(posx > x) del1(posx --);
		while(posy < y) add2(++ posy);
		while(posy > y) del2(posy --);
		
		ans[q[i].id] += res * q[i].val;
	}
	
	for(int i = 1 ; i <= m ; ++ i)
		cout << ans[i] << '\n';
	
	return 0;
}

CF617E XOR and Favorite Number

  1. 区间 \([L, R]\) 的异或和,可以用 \(pre_r \oplus pre_{l - 1}\) 得到。
  2. 对于一次询问 \([ql, qr]\),本质上是找多少点对的 \(pre\) 异或和为 \(k\)
  3. 对于区间 \([qx, qy]\),枚举子区间终点 \(i\),则能够与 \(i\) 搭配作为起点 \(s\) 的值要满足 \(pre_s \oplus pre_i = k\)
  4. 维护 \(cnt_{sum_i \oplus k}\) 的数量,即 \(s\) 的数量。
  5. 注意 \(s\) 是子区间 \([L, R]\)\(L - 1\)

代码:

#include <bits/stdc++.h>
#define int long long
using namespace std;

const int N = 4e6 + 5;
int n, Q, k, l, r, res, a[N], sum[N], ans[N];

namespace Block {
	int kc, id[N], buc[N];
	struct Node {
		int l, r, id;
	} q[N];
	
	inline bool cmp(Node a, Node b) {
		if(id[a.l] == id[b.l]) {
			if(id[a.l] & 1) return a.r > b.r;
			
			return a.r < b.r; 
		}
		
		return id[a.l] < id[b.l];
	}
	
	inline void init() {
		kc = sqrt(n);
		
		for(int i = 1 ; i <= n ; ++ i)
			id[i] = (i - 1) / kc + 1;
		
		sort(q + 1, q + 1 + Q, cmp);
		
		return ;
	}
	
	inline void add(int x) {
		res += buc[sum[x] ^ k];
		++ buc[sum[x]];
		
		return ;
	}
	
	inline void del(int x) {
		-- buc[sum[x]];
		res -= buc[sum[x] ^ k];
		
		return ;
	}
}

using namespace Block;

signed main() {
	ios_base :: sync_with_stdio(NULL);
	cin.tie(nullptr);
	cout.tie(nullptr);
	
	cin >> n >> Q >> k;
	for(int i = 1 ; i <= n ; ++ i) {
		cin >> a[i];
		
		sum[i] = sum[i - 1] ^ a[i];
	}
	for(int i = 1 ; i <= Q ; ++ i) {
		cin >> l >> r; 
		
		q[i] = {l - 1, r, i};
	}
	
	init();
	
	int posl = 1, posr = 0;
	
	for(int i = 1 ; i <= Q ; ++ i) {
		int l = q[i].l, r = q[i].r;
		
		while(posl > l) add(-- posl);
		while(posr < r) add(++ posr); 
		while(posl < l) del(posl ++);
		while(posr > r) del(posr --);
		
		ans[q[i].id] = res;
	}
	
	for(int i = 1 ; i <= Q ; ++ i)
		cout << ans[i] << '\n'; 
	
	return 0;
}

P1903 [国家集训队] 数颜色 / 维护队列

带修莫队模板题,注意撤销修改的影响。

代码:

#include <bits/stdc++.h>
#define int long long
using namespace std;

const int N = 1.5e5 + 5;
const int M = 1e6 + 5;
int n, Q, l, r, t, x, k, a[N], ans[N];
char op;

namespace Block {
	int kc, res, cntq, cntr, id[N], cnt[M];
	struct Ques {
		int l, r, x, id;
	} q[N];
	struct Adds {
		int x, k;
	} c[N];
	
	inline bool cmp(Ques a, Ques b) {
		if(id[a.l] == id[b.l]) {
			if(id[a.r] == id[b.r]) return a.x < b.x;
			
			return id[a.r] < id[b.r];
		}
		
		return id[a.l] < id[b.l];
	}
	
	inline void init() {
		kc = pow(n, 2.0 / 3.0);
		
		for(int i = 1 ; i <= n ; ++ i)
			id[i] = (i - 1) / kc + 1;
		
		sort(q + 1, q + 1 + cntq, cmp);
		
		return ;
	}
	
	inline void add(int x) {
		if(! cnt[a[x]]) ++ res;
		
		++ cnt[a[x]];
		
		return ;
	}
	
	inline void del(int x) {
		-- cnt[a[x]];
		
		if(! cnt[a[x]]) -- res;
		
		return ;
	}
	
	inline void modify(int x, int t) {
		if(c[t].x >= q[x].l && c[t].x <= q[x].r) {
			del(c[t].x);
			int tmp = a[c[t].x];
			
			a[c[t].x] = c[t].k;
			add(c[t].x);
			
			a[c[t].x] = tmp;
		}
		
		swap(a[c[t].x], c[t].k);
		
		return ;
	}
}

using namespace Block;

signed main() {
	ios_base :: sync_with_stdio(NULL);
	cin.tie(nullptr);
	cout.tie(nullptr);
	
	cin >> n >> Q;
	for(int i = 1 ; i <= n ; ++ i)
		cin >> a[i];
	for(int i = 1 ; i <= Q ; ++ i) {
		cin >> op;
		
		if(op == 'Q') {
			cin >> l >> r;
			
			++ cntq;
			q[cntq] = {l, r, cntr, cntq};
		}
		else {
			cin >> x >> k;
			
			++ cntr;
			c[cntr] = {x, k};
		}
	}
	
	init();
	
	int posl = 1, posr = 0, post = 0;
	
	for(int i = 1 ; i <= cntq ; ++ i) {
		l = q[i].l, r = q[i].r, t = q[i].x;
		
		while(posl > l) add(-- posl);
		while(posr < r) add(++ posr);
		while(posl < l) del(posl ++);
		while(posr > r) del(posr --);
		while(post < t) modify(i, ++ post);
		while(post > t) modify(i, post --); 
		
		ans[q[i].id] = res;
	}
	
	for(int i = 1 ; i <= cntq ; ++ i)
		cout << ans[i] << '\n';
	
	return 0;
}

P3709 大爷的字符串题

  1. 对于询问 \([L, R]\),每次贪心地插入一个严格上升在子序列最优。
  2. 求区间可以划分为最少几个严格上升子序列。
  3. 结论是区间内众数的出现次数即为答案。
  • 证明:假设众数出现次数为 \(cnt\),则至少需要 \(cnt\) 个严格上升子序列。众数以外的其余元素一定可以分在不同的严格上升子序列中,则每个分组无重复元素至多 \(cnt\) 个。
  1. 于是用莫队求众数即可。

代码:

#include <bits/stdc++.h>
#define int long long
using namespace std;

const int N = 2e5 + 5;
int n, Q, l, r, a[N], lsh[N], ans[N];

namespace Block {
	int kc, res, id[N], cnt[N], buc[N];
	struct Node {
		int l, r, id;
	} q[N];
	
	inline bool cmp(Node a, Node b) {
		if(id[a.l] == id[b.l]) {
			if(id[a.l] & 1) return a.r > b.r;
			
			return a.r < b.r;
		}
		
		return id[a.l] < id[b.l];
	}
	
	inline void init() {
		kc = sqrt(n);
		
		for(int i = 1 ; i <= n ; ++ i)
			id[i] = (i - 1) / kc + 1;
		
		sort(q + 1, q + 1 + Q, cmp);
		
		return ;
	}
	
	inline void add(int x) {
		-- cnt[buc[a[x]]];
		++ buc[a[x]];
		++ cnt[buc[a[x]]];
		
		res = max(res, buc[a[x]]);
		
		return ;
	}
	
	inline void del(int x) {
		if(res == buc[a[x]] && cnt[buc[a[x]]] == 1) -- res;
		
		-- cnt[buc[a[x]]];
		-- buc[a[x]];
		++ cnt[buc[a[x]]];
		
		return ;
	}
}

using namespace Block;

signed main() {
	ios_base :: sync_with_stdio(NULL);
	cin.tie(nullptr);
	cout.tie(nullptr);
	
	cin >> n >> Q;
	for(int i = 1 ; i <= n ; ++ i)
		cin >> a[i], lsh[i] = a[i];
	for(int i = 1 ; i <= n ; ++ i)
		cin >> l >> r, q[i] = {l, r, i};
	
	init();
	
	sort(lsh + 1, lsh + 1 + n);
	int tot = unique(lsh + 1, lsh + 1 + n) - lsh - 1;
	
	for(int i = 1 ; i <= n ; ++ i)
		a[i] = lower_bound(lsh + 1, lsh + 1 + tot, a[i]) - lsh;
	
	int posl = 1, posr = 0;
	
	for(int i = 1 ; i <= Q ; ++ i) {
		l = q[i].l, r = q[i].r;
		
		while(posl > l) add(-- posl);
		while(posr < r) add(++ posr);
		while(posl < l) del(posl ++);
		while(posr > r) del(posr --);
		
		ans[q[i].id] = res;
	}
	
	for(int i = 1 ; i <= Q ; ++ i)
		cout << -ans[i] << '\n';
	
	return 0;
}

U41492 树上数颜色

树上莫队板题,拍成 dfs 序后莫队即可。

代码:

#include <bits/stdc++.h>
#define int long long
#define pb emplace_back
using namespace std;

const int N = 1e5 + 5;
int n, Q, u, v, res, heavy, a[N], sz[N], ans[N], cnt[N], son[N];
vector<int> g[N], q[N];

inline void dfs1(int x, int last) {
    sz[x] = 1;

    for(auto u : g[x])
        if(u != last) {
            dfs1(u, x);

            sz[x] += sz[u];
            if(sz[u] > sz[son[x]]) son[x] = u;
        }

    return ;
}

inline void calc(int x, int last, int val) {
    if(! cnt[a[x]]) ++ res;
    cnt[a[x]] += val;
    if(! cnt[a[x]]) -- res;

    for(auto u : g[x])
        if(u != last && u != heavy) calc(u, x, val);

    return ;
}

inline void dfs2(int x, int last, bool flag) {
    for(auto u : g[x])
        if(u != last && u != son[x]) dfs2(u, x, 0);

    if(son[x]) dfs2(son[x], x, 1), heavy = son[x];

    calc(x, last, 1);

    for(auto i : q[x])
        ans[i] = res;

	heavy = 0;

    if(! flag) calc(x, last, -1);

    return ;
}

signed main() {
    ios_base :: sync_with_stdio(NULL);
    cin.tie(nullptr);
    cout.tie(nullptr);

    cin >> n;
    for(int i = 1 ; i < n ; ++ i) {
        cin >> u >> v;

        g[u].pb(v), g[v].pb(u);
    }
    for(int i = 1 ; i <= n ; ++ i)
        cin >> a[i];
    cin >> Q;
    for(int i = 1 ; i <= Q ; ++ i) {
        cin >> u;

        q[u].pb(i);
    }

    dfs1(1, -1);
    dfs2(1, -1, 0);

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

    return 0;
}

P3245 [HNOI2016] 大数

维护后缀和,推式子后莫队即可,类比 Hash 具体过程。

代码:

#include <bits/stdc++.h>
#define int long long
#define ull unsigned long long
using namespace std;

const int N = 2e5 + 5;
int p, n, Q, res, ans[N], sum[N], lsh[N];
string s;

namespace Block {
    int kc, id[N], cnt[N];

    struct Node {
        int l, r, id;
    } q[N];

    inline bool cmp(Node a, Node b) {
        if(id[a.l] == id[b.l]) {
            if(id[a.l] & 1) return a.r < b.r;

            return a.r > b.r;
        }

        return id[a.l] < id[b.l];
    }

    inline void init() {
        kc = sqrt(n);

        for(int i = 1 ; i <= n ; ++ i)
            id[i] = (i - 1) / kc + 1;
        
        id[n + 1] = n / kc + 1;

        sort(q + 1, q + 1 + Q, cmp);

        return ;
    }

    inline void add(int x) {
        res += cnt[sum[x]];
        ++ cnt[sum[x]];

        return ;
    }

    inline void del(int x) {
        -- cnt[sum[x]];
        res -= cnt[sum[x]];

        return ;
    }
}

using namespace Block;

signed main() {
    ios_base :: sync_with_stdio(NULL);
    cin.tie(nullptr);
    cout.tie(nullptr);

    cin >> p >> s >> Q;
    for(int i = 1 ; i <= Q ; ++ i) {
        cin >> q[i].l >> q[i].r;

        ++ q[i].r, q[i].id = i;
    }

    n = s.size();
    s = " " + s;

    if(p == 2 || p == 5) {
        for(int i = 1 ; i <= n ; ++ i) {
            if((s[i] - '0') % p == 0) ans[i] += i, ++ cnt[i];

            ans[i] += ans[i - 1];
            cnt[i] += cnt[i - 1];
        }

        for(int i = 1 ; i <= Q ; ++ i)
            cout << ans[q[i].r - 1] - ans[q[i].l - 1] - (cnt[q[i].r - 1] - cnt[q[i].l - 1]) * (q[i].l - 1) << '\n';

        return 0;
    }

    init();

    int pow = 1;
    
    ++ n;
    sum[n] = lsh[n] = 0;

    for(int i = n - 1 ; i ; -- i) {
        sum[i] = (sum[i + 1] + (s[i] - '0') * pow % p) % p;
        lsh[i] = sum[i];

        pow = pow * 10 % p;
    }

    sort(lsh + 1, lsh + 1 + n);
    int tot = unique(lsh + 1, lsh + 1 + n) - lsh - 1;
    
    for(int i = 1 ; i <= n ; ++ i)
        sum[i] = lower_bound(lsh + 1, lsh + 1 + tot, sum[i]) - lsh;

    int posl = 1, posr = 0;
    
    for(int i = 1 ; i <= Q ; ++ i) {
        int l = q[i].l, r = q[i].r;

        while(posl > l) add(-- posl);
        while(posr < r) add(++ posr);
        while(posl < l) del(posl ++);
        while(posr > r) del(posr --);

        ans[q[i].id] = res;

        // cerr << "id:" << q[i].id << '\n';
    }

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

    return 0;
}

P3604 美好的每一天

由于回文串只考虑单个字符出现次数的奇偶性,因此考虑状压字符集。满足回文串的要求是出现次数为奇数的字符不超过 \(1\),对于每一位去算一遍即可。

时间复杂度 \(O(n \sqrt n|\Sigma| + 2 ^ {|\Sigma|})\)

代码:

#include <bits/stdc++.h>
//#define int long long
#define ll long long
using namespace std;

const int N = 6e4 + 5;
const int SIGMA = 27;
int n, Q, l, r, a[N], buc[1 << (SIGMA + 1)];
ll res, ans[N];
bool pre[N][SIGMA];
struct Node {
	int l, r, id;
} q[N];
char ch;

namespace Block {
	int kc, id[N];
	
	inline void init() {
		kc = sqrt(n);
		
		for(int i = 1 ; i <= n ; ++ i)
			id[i] = (i - 1) / kc + 1;
		
		sort(q + 1, q + 1 + Q, [&](Node a, Node b) {
			if(id[a.l] == id[b.l]) {
				if(id[a.l] & 1) return a.r > b.r;
				else return a.r < b.r;
			}
			else return id[a.l] < id[b.l];
		});
			
		return ;
	}
	
	inline int work(int pos) {
		int res = 0;
		
		for(int i = 1 ; i <= 26 ; ++ i)
			if(pre[pos][i]) res += (1 << i);
			
		return res;
	}
	
	inline void add(int x) {
		int num = work(x);
		
		res += buc[num];
		for(int i = 1 ; i <= 26 ; ++ i)
			res += buc[num ^ (1 << i)];
		
		++ buc[num];
			
		return ;
	}
	
	inline void del(int x) {
		int num = work(x);
		
		-- buc[num];
		
		res -= buc[num];
		for(int i = 1 ; i <= 26 ; ++ i)
			res -= buc[num ^ (1 << i)];
			
		return ;
	}
}

using namespace Block;

signed main() {
	ios_base :: sync_with_stdio(NULL);
	cin.tie(nullptr);
	cout.tie(nullptr);
	
	cin >> n >> Q;
	for(int i = 1 ; i <= n ; ++ i) {
		cin >> ch;
		
		a[i] = ch - 'a' + 1;
		
		for(int j = 1 ; j <= 26 ; ++ j)
			pre[i][j] = pre[i - 1][j];
		pre[i][a[i]] ^= 1;
	}
	for(int i = 1 ; i <= Q ; ++ i) {
		cin >> l >> r;
		
		q[i] = {l - 1, r, i};
	}
	
	init();
	
	int posl = 1, posr = 0;
	
	for(int i = 1 ; i <= Q ; ++ i) {
		l = q[i].l, r = q[i].r;
		
		while(posl > l) add(-- posl);
		while(posr < r) add(++ posr);
		while(posl < l) del(posl ++);
		while(posr > r) del(posr --);
		
		ans[q[i].id] = res;
	}
	
	for(int i = 1 ; i <= Q ; ++ i)
		cout << ans[i] << '\n';
	
	return 0;
}
posted @ 2025-07-05 19:42  endswitch  阅读(52)  评论(0)    收藏  举报