22ZRC做题笔记
Day 1
萌萌哒
一个长度为 \(n\) 的大数,用 \(S_1S_2S_3 \cdots S_n\)表示,其中 \(S_i\) 表示数的第 \(i\) 位, \(S_1\) 是数的最高位。告诉你一些限制条件,每个条件表示为四个数,\(l_1,r_1,l_2,r_2\),即两个长度相同的区间,表示子串 \(S_{l_1}S_{l_1+1}S_{l_1+2} \cdots S_{r_1}\) 与 \(S_{l_2}S_{l_2+1}S_{l_2+2} \cdots S_{r_2}\) 完全相同。
比如 \(n=6\) 时,某限制条件 \(l_1=1,r_1=3,l_2=4,r_2=6\) ,那么 \(123123\),\(351351\) 均满足条件,但是 \(12012\),\(131141\) 不满足条件,前者数的长度不为 \(6\) ,后者第二位与第五位不同。问满足以上所有条件的数有多少个。
\(n\le 10^5\)。
首先我们有暴力做法,将限制条件两个区间的每个点合并,比如区间 \([1,2]\) 的 \(1\),和区间 \([3,4]\) 的 \(3\) 合并。这样做的原因是因为这两个点必须取同样的值。合并完之后,若连通块个数有 \(k\) 个,答案即为 \(10^{k-1} \times 9\)。这样时间复杂度是 \(O(nm\log n)\) 的,无法通过。
我们有一种好的做法是将整个序列倍增,这样我们一共就有 \(O(n\log n)\) 区间。然后对于一个操作,我们可以整体考虑,将这个各两个区间分成两个倍增区间。然后在分别合并四个区间。这样我们就会得到每个起点为 \(i\),长度为 \(2^{k}\) 的区间的所属集合。然后我们从上往下传递所属集合,比如 \([1,4] \in [5,8]\),传递变成 \([1,2] \in [5,6],[3,4] \in [7,8]\)。注意我们一层最多传递 \(O(n)\) 次,因为我们有并查集。最后在 \(k=1\) 一层统计连通块个数即可。时间复杂度 \(O(n\log^2 n)\)。
本质上,我们在上层就复合了信息,再传递到下层,减少了普通下层的操作次数。
// Problem: P3295 [SCOI2016] 萌萌哒
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P3295
// Memory Limit: 125 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
// 这回只花了 1s 就打完了。
// 真好。记得多手造几组。最好有暴力对拍。
#include <bits/stdc++.h>
#define int long long
#define upp(a, x, y) for (int a = x; a <= y; a++)
#define dww(a, x, y) for (int a = x; a >= y; a--)
#define pb(x) push_back(x)
#define endl '\n'
#define x first
#define y second
#define PII pair<int, int>
using namespace std;
const int N = 1e6 + 10, X = 1e9 + 7;
int qmi(int a, int b, const int P) {
int res = 1;
while (b) {
if (b & 1) res = res * a % P;
a = a * a % P;
b >>= 1;
}
return res;
}
int fa[N][21], n, m;
int find(int x, int k) {
if (fa[x][k] == x) return x;
return fa[x][k] = find(fa[x][k], k);
}
void solve() {
cin >> n >> m;
upp(j, 0, 20) upp(i, 1, n - (1 << j) + 1) fa[i][j] = i;
upp(i, 1, m) {
int l1, r1, l2, r2;
cin >> l1 >> r1 >> l2 >> r2;
int len = log2(r1 - l1 + 1);
fa[find(l1, len)][len] = find(l2, len);
fa[find(r1 - (1 << len) + 1, len)][len] =
find(r2 - (1 << len) + 1, len);
}
int cnt = 0;
dww(k, 20, 1) {
upp(i, 1, (n - (1 << k) + 1)) {
int fx = find(i, k);
fa[find(i, k - 1)][k - 1] = find(fx, k - 1);
fa[find(i + (1 << k - 1), k - 1)][k - 1] =
find(fx + (1 << k - 1), k - 1);
}
}
upp(i, 1, n) {
if (find(i, 0) == i) cnt++;
}
cout << (qmi(10, (cnt - 1), X) * 9) % X << endl;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int tt = 1;
while (tt--) solve();
return 0;
}
冰火战士
有 \(q(q\le 2\times 10^6)\) 次询问,在集合 \(A\) 或者 \(B\) 中插入二元组 \((x,y)(x\le 2\times 10^9,\sum y \le 2\times 10^9)\) 或者删去一个集合里的二元组。在每次询问后输出:
\[\begin{aligned} \large \max_{t}\{\min(\sum_{p\in A}[p_{x}\le t]y,\sum_{p\in B}[p_{x}\ge t]y)\} \end{aligned} \]以及取到这个值的最大 \(t\)。
假设这个式子是一个关于 \(t\) 的函数 \(f(t)\)。
很明显,这个函数是一个峰函数,因此我们只需要能够快速计算和式即可做到 \(O(Pq \log n)\),\(P\) 为计算和式的时间,利用树状数组和离散化,我们可以做到 \(P=\log n\),但是这样仍然不能够通过此题。
这个时候考虑将二分和查询结合化,企图找到 \(O(q\log n)\) 的算法。首先我们可以在线段树里二分,这是很简单的,可是线段树常数太大了,于是我们考虑树状数组,可是树状数组实际上的是倍增的信息,所以我们将二分转化为倍增即可在树状数组上倍增,得到 \(O(q\log n)\) 的时间复杂度。
具体的,我们从 \(2\) 高次幂开始查询,由于树状数组维护的 \(c_{i}=\sum_{j=i-lowbit(i)+1}^{i} a_{j}\),所以可以顺手维护前缀和,然后符合条件就将 \(c_{now}\) 加入到 \(sum\) 里。最后即可得到希望符合条件的位置。
然后我们可以把 \(\sum_{p\in A}[p_{x}\le t]y\le \sum_{p\in B}[p_{x}\ge t]y\) 的最右边位置 \(t\) 给找出来。这个时候我们已经离散化了,于是查看 \(f(t+1)\) 是否大于等于 \(f(t)\),如果小于,直接输出 \(t\) 的对应数即可。否则我们开一个 set 维护所有在 \(B\) 集合中的数,二分第一个大于等于 \(t+1\) 的 \(B\) 集合中的数,输出对应数即可,这样做是因为此时我们的函数取值在于第二个式子,而第二个式子的函数关于离散化后的取值是非严格单调递减的,所以直接取最大的不影响第二个式子取值的位置就行了,这个位置就是最小的大于等于 \(t+1\) 的在 \(B\) 集合中的数。
// Problem: P6619 [省选联考 2020 A/B 卷] 冰火战士
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P6619
// Memory Limit: 512 MB
// Time Limit: 3000 ms
//
// Powered by CP Editor (https://cpeditor.org)
// 这回只花了 1s 就打完了。
// 真好。记得多手造几组。最好有暴力对拍。
#include <bits/stdc++.h>
#define upp(a, x, y) for (int a = x; a <= y; a++)
#define dww(a, x, y) for (int a = x; a >= y; a--)
#define pb(x) push_back(x)
#define endl '\n'
#define x first
#define y second
#define PII pair<int, int>
using namespace std;
const int N = 4e6 + 10;
int t1[N], t2[N];
int a1[N], a2[N];
unordered_map<int, int> ha;
vector<int> nums;
unordered_map<int, int> inb;
int cnt, all;
struct node {
int op, t, x, y;
} opr[N];
int qq;
int lowbit(int x) { return x & -x; }
void add(int x, int k, int t[]) {
for (int i = x; i <= qq; i += lowbit(i)) t[i] += k;
}
int qry(int x, int t[]) {
if (x < 0) return 0;
int sum = 0;
for (int i = x; i; i -= lowbit(i)) sum += t[i];
return sum;
}
int qryall(int l, int r, int t[]) { return qry(r, t) - qry(l - 1, t); }
int find() {
int x = 0, sum1 = 0, sum2 = 0;
dww(i, (int)log2(qq), 0) {
int tmp = x + (1 << i);
if (sum1 + t2[tmp] <= (all - t1[tmp] - sum2) + a1[tmp]) {
sum1 += t2[tmp];
sum2 += t1[tmp];
x = tmp;
}
}
return x;
}
void solve() {
cin >> qq;
upp(i, 1, qq) {
int op;
cin >> op;
opr[i].op = op;
if (op == 1) {
cin >> opr[i].t >> opr[i].x >> opr[i].y;
nums.push_back(opr[i].x);
} else
cin >> opr[i].t;
}
sort(nums.begin(), nums.end());
upp(i, 0, nums.size() - 1) {
if (!ha[nums[i]]) {
ha[nums[i]] = ++cnt, inb[cnt] = nums[i];
}
}
upp(i, 1, qq) {
if (opr[i].op == 1) {
opr[i].x = ha[opr[i].x];
} else {
opr[i] = opr[opr[i].t];
opr[i].y *= -1;
}
}
multiset<int> S;
upp(i, 1, qq) {
if (opr[i].op == 1) {
if (!opr[i].t) {
add(opr[i].x, opr[i].y, t2);
a2[opr[i].x] += opr[i].y;
} else {
add(opr[i].x, opr[i].y, t1);
a1[opr[i].x] += opr[i].y;
all += opr[i].y;
if (opr[i].y < 0) {
auto iter = S.lower_bound(opr[i].x);
S.erase(iter);
} else {
S.insert(opr[i].x);
}
}
}
int l = find();
int q1 = qryall(1, l, t2), q2 = qryall(l, qq, t1);
int q3 = q1 + a2[l + 1], q4 = q2 - a1[l];
if (min(q1, q2) > min(q3, q4)) {
cout << inb[l] << ' ' << min(q1, q2) * 2 << endl;
} else {
if (min(q3, q4) == 0) {
cout << "Peace" << endl;
} else {
int tmp = l + 1;
int op = (*S.lower_bound(tmp));
cout << inb[(*S.lower_bound(tmp))] << ' ' << min(q3, q4) * 2
<< endl;
}
}
}
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int tt = 1;
while (tt--) solve();
return 0;
}
白雪皑皑
现在有 \(n\) 片雪花排成一列。 pty 要对雪花进行 \(m\) 次染色操作,第 \(i\) 次染色操作中,把第 \(((i\times p+q)\bmod n)+1\) 片雪花和第 \(((i\times q+p)\bmod n)+1\) 片雪花之间的雪花(包括端点)染成颜色 \(i\)。其中 \(p,q\) 是给定的两个正整数。他想知道最后 \(n\) 片雪花被染成了什么颜色。没有被染色输出 \(0\)。
\(m\le 10^7,n\le 10^6\)。
考虑倒着做之后,后面的操作就不能覆盖前面的操作。此时最多有 \(n\) 次数组修改。可是重点在于要在短时间内找到没有被改过的位置。你可能会想到记录每个点右边(包含自己)第一个没染过色的点。这样我们询问是容易的,可以询问怎么办呢?我们可以推到后面,因为它被染色了,此时的询问答案是 \(i+1\) 位置的询问答案,如果我们这样一直推,就可以推到第一个没染过色的格子,或者结尾。这相当于 \(i\) 和 \(i+1\) 属于一个集合。
因此,我们只需要在染色的时候合并 \((i,i+1)\) 即可,然后跳转到 \(i\) 的祖先再继续检查是否小于等于 \(r\),然后染色即可。这里注意初始化 \(n+1\) 一开始的祖先是 \(n+1\)。
// Problem: P2391 白雪皑皑
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P2391
// Memory Limit: 512 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
// 这回只花了 1s 就打完了。
// 真好。记得多手造几组。最好有暴力对拍。
#include <bits/stdc++.h>
#define int long long
#define upp(a, x, y) for (int a = x; a <= y; a++)
#define dww(a, x, y) for (int a = x; a >= y; a--)
#define pb(x) push_back(x)
#define endl '\n'
#define x first
#define y second
#define PII pair<int, int>
using namespace std;
const int N = 1e7 + 10;
struct node {
int l, r, k;
} opr[N];
int n, p, q, m;
int fa[N], a[N]; // i 右边第一个不为 0 的数
int find(int x) {
if (fa[x] != x) return fa[x] = find(fa[x]);
return x;
}
void merge(int x, int y) { fa[find(x)] = find(y); }
void solve() {
cin >> n >> m >> p >> q;
upp(i, 1, m) {
opr[i].l = (i * p + q) % n + 1;
opr[i].r = (i * q + p) % n + 1;
opr[i].k = i;
if (opr[i].l > opr[i].r) swap(opr[i].l, opr[i].r);
}
upp(i, 1, n) fa[i] = i;
fa[n+1]=n+1;
dww(i, m, 1) {
int l = opr[i].l, r = opr[i].r, k = opr[i].k;
int now = find(l);
while (now <= r) {
a[now] = k;
merge(now, now + 1);
now = find(now);
}
}
upp(i, 1, n) cout << a[i] << endl;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int tt = 1;
while (tt--) solve();
return 0;
}
高速公路
Y901 高速公路是一条由 \(n-1\) 段路以及 \(n\) 个收费站组成的东西向的链,我们按照由西向东的顺序将收费站依次编号为 \(1 \sim n\),从收费站 \(i\) 行驶到 \(i+1\)(或从 \(i+1\) 行驶到 \(i\))需要收取 \(v_i\) 的费用。高速路刚建成时所有的路段都是免费的,即所有 \(v_i = 0\)。
政府部门根据实际情况,会不定期地对连续路段的收费标准进行调整,根据政策涨价或降价。
无聊的小 A 同学总喜欢研究一些稀奇古怪的问题,他开车在这条高速路上行驶时想到了这样一个问题:对于给定的 \(l,r\),在第 \(l\) 个到第 \(r\) 个收费站里等概率随机取出两个不同的收费站 \(a\) 和 \(b\),那么从 \(a\) 行驶到 \(b\) 将期望花费多少费用呢?
\(1\le n,m\le 10^5\)。
考虑我们本质是求 \(\frac{2\displaystyle \sum_{i=l}^{r} \sum_{j=i}^{r} \sum_{k=i}^{j} v_{k}}{(r-l+1)\times (r-l)}\)。
让我们稍微做一点变换:
至此,我们可以发现无论是对于 \(\sum_{j} v_{j},\sum_{j} jv_{j}\),还是 \(\sum_{j} j^2v_{j}\),我们都可以使用树状数组来维护。
因此,我们用树状数组乘上相应的系数,然后计算即可。
古早代码:
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5 + 5;
struct node {
int sum1, sum2, sum3, sum4, sum5;
int lz, l, r;
#define l(x) tr[x].l
#define r(x) tr[x].r
#define lz(x) tr[x].lz
#define sum1(x) tr[x].sum1
#define sum2(x) tr[x].sum2
#define sum3(x) tr[x].sum3
#define sum4(x) tr[x].sum4
#define sum5(x) tr[x].sum5
};
int gcd(int x, int y) { return y ? gcd(y, x % y) : x; }
struct segment_tree {
node tr[N * 4];
int root = 1;
node merge(node a, node b) {
return node{a.sum1 + b.sum1,
a.sum2 + b.sum2,
a.sum3 + b.sum3,
a.sum4 + b.sum4,
a.sum5 + b.sum5,
0,
0,
0};
}
void pu(int x) {
sum1(x) = sum1(x * 2) + sum1(x * 2 + 1);
sum2(x) = sum2(x * 2) + sum2(x * 2 + 1);
sum3(x) = sum3(x * 2) + sum3(x * 2 + 1);
sum4(x) = sum4(x * 2) + sum4(x * 2 + 1);
sum5(x) = sum5(x * 2) + sum5(x * 2 + 1);
}
void pd(int x) {
if (lz(x)) {
sum1(x * 2) += (r(x * 2) - l(x * 2) + 1) * lz(x);
sum1(x * 2 + 1) += (r(x * 2 + 1) - l(x * 2 + 1) + 1) * lz(x);
sum2(x * 2) += lz(x) * sum4(x * 2);
sum2(x * 2 + 1) += lz(x) * sum4(x * 2 + 1);
sum3(x * 2) += lz(x) * sum5(x * 2);
sum3(x * 2 + 1) += lz(x) * sum5(x * 2 + 1);
lz(x * 2) += lz(x), lz(x * 2 + 1) += lz(x);
lz(x) = 0;
}
}
void build(int x, int l, int r) {
l(x) = l, r(x) = r;
if (l == r) {
sum1(x) = sum2(x) = sum3(x) = 0;
sum4(x) = l, sum5(x) = l * l;
return;
}
int mid = l + r >> 1;
build(x * 2, l, mid), build(x * 2 + 1, mid + 1, r);
pu(x);
}
void change(int x, int ll, int rr, int k) {
int l = l(x), r = r(x);
if (l >= ll && r <= rr) {
sum1(x) += (r - l + 1) * k;
sum2(x) += sum4(x) * k;
sum3(x) += sum5(x) * k;
lz(x) += k;
return;
}
pd(x);
int mid = l + r >> 1;
if (mid >= ll) change(x * 2, ll, rr, k);
if (mid < rr) change(x * 2 + 1, ll, rr, k);
pu(x);
}
node qry(int x, int ll, int rr) {
int l = l(x), r = r(x);
if (l >= ll && r <= rr) {
return tr[x];
}
pd(x);
int mid = l + r >> 1, sum = 0;
node c;
if (ll <= mid) {
if (mid < rr)
c = merge(qry(x * 2, ll, rr), qry(x * 2 + 1, ll, rr));
else
c = qry(x * 2, ll, rr);
} else
c = qry(x * 2 + 1, ll, rr);
pu(x);
return c;
}
} tree;
int n, m;
signed main() {
cin >> n >> m;
n--;
tree.build(1, 1, n);
while (m--) {
char op;
int l, r, v;
cin >> op;
if (op == 'C') {
cin >> l >> r >> v;
tree.change(1, l, r - 1, v);
} else {
cin >> l >> r;
int a, b;
node c = tree.qry(1, l, r);
a = c.sum2 * (l + r - 1) - c.sum3 + c.sum1 * (r - l * r);
b = (r - l + 1) * (r - l) / 2;
int k = gcd(a, b);
cout << a / k << '/' << b / k << endl;
}
}
return 0;
}
Day 2
DZY Loves Fibonacci Numbers
给定你一个长度为 \(n\) 序列,有 \(q\) 次操作,每次操作如下:
- 给定 \(l,r\),将 \([l,r]\) 的 \(a_{i}\) 加上 \(F_{i-l+1}\)(\(F_{i}\) 表示斐波那契数列的第 \(i\) 项)。
- 给定 \(l,r\),求 \(\displaystyle \sum_{i=l}^{r} a_{i}\)。
\(1 \le n,m \le 3\times 10^5\)。
注:\(F_{1}=1,F_{2}=1,F_{n}=F_{n-1}+F_{n-2}(n\ge 3)\)。
由于所有操作和 \(a\) 没有关系,所以可以把原序列的贡献和操作的贡献拆开考虑。
考虑如何实现操作?我们有斐波那契的通项公式:
容易发现,括号里面的两项分别是关于 \(n\) 的等比数列,我们可以使用等比数列前缀和的公式求解,并且等比数列前缀和的值是一个仅关于序列的首项的函数乘上一个只和比有关的系数,所以对于区间 \([l,r]\),我们可以累加不同操作的 \(l\) 函数的值,因为有结合律,所以可以直接累加合并操作(标记)。然后对于下传标记来说,序列的首项的函数的值适用于左区间,而右区间只需要乘上比的乘方即可得到下传的右区间的系数。
而 \(\sqrt{5}\) 以及其衍射物可以通过二次剩余求解,而 \(\sqrt{5}\) 在 \(P=10^9+9\) 的意义下存在二次剩余,然后 \(\frac{1}{\sqrt{5}}\) 无非就是 \(\sqrt{5}\) 的逆元。
// Problem: DZY Loves Fibonacci Numbers
// Contest: Virtual Judge - CodeForces
// URL: https://vjudge.net/problem/CodeForces-446C#author=GPT_zh
// Memory Limit: 256 MB
// Time Limit: 4000 ms
//
// Powered by CP Editor (https://cpeditor.org)
// 这回只花了 1s 就打完了。
// 真好。记得多手造几组。最好有暴力对拍。
#include <bits/stdc++.h>
#define int long long
#define mid (l + r) / 2
#define upp(a, x, y) for (int a = x; a <= y; a++)
#define dww(a, x, y) for (int a = x; a >= y; a--)
#define pb(x) push_back(x)
#define endl '\n'
#define x first
#define y second
#define PII pair<int, int>
using namespace std;
const int N = 3e5 + 10, X = 1e9 + 9, T = 383008016;
int a[N], sum[N], n, m, INV1;
struct node {
int l, r, sum, lz;
#define l(x) tr[x].l
#define r(x) tr[x].r
#define sum(x) tr[x].sum
#define lz(x) tr[x].lz
};
int qmi(int a, int b, const int P) {
int res = 1;
while (b) {
if (b & 1) res = res * a % P;
a = a * a % P;
b >>= 1;
}
return res;
}
struct segtree {
node tr[N * 4];
int m[N], INV2;
void init(int op) {
if (op)
INV2 = qmi((1 - T + X) * INV1 % X, X - 2, X);
else
INV2 = qmi((1 + T) % X * INV1 % X, X - 2, X);
m[0] = 1;
upp(i, 1, n) {
if (op)
m[i] = m[i - 1] * ((1 + T) * INV1 % X) % X;
else
m[i] = m[i - 1] * ((1 - T + X) * INV1 % X) % X;
}
}
void pu(int x) { sum(x) = (sum(x * 2) + sum(x * 2 + 1)) % X; }
void apl(int x, int k) {
(lz(x) += k) %= X;
(sum(x) += k * (1 - m[r(x) - l(x) + 1] + X) % X * INV2 % X) %= X;
}
void pd(int x) {
if (lz(x)) {
int l = l(x), r = r(x);
apl(x * 2, lz(x));
apl(x * 2 + 1, lz(x) * m[mid - l + 1] % X);
lz(x) = 0;
}
}
void build(int x, int l, int r) {
l(x) = l, r(x) = r, lz(x) = 0;
if (l == r) {
sum(x) = 0;
return;
}
build(x * 2, l, mid);
build(x * 2 + 1, mid + 1, r);
pu(x);
}
void change(int x, int ll, int rr) {
int l = l(x), r = r(x);
if (l >= ll && r <= rr) {
apl(x, m[l - ll + 1]);
return;
}
pd(x);
if (ll <= mid) change(x * 2, ll, rr);
if (rr > mid) change(x * 2 + 1, ll, rr);
pu(x);
}
int qry(int x, int ll, int rr) {
int l = l(x), r = r(x);
if (l >= ll && r <= rr) return sum(x);
int sum = 0;
pd(x);
if (ll <= mid) (sum += qry(x * 2, ll, rr)) % X;
if (rr > mid) (sum += qry(x * 2 + 1, ll, rr)) % X;
pu(x);
return sum;
}
} t1, t2;
void solve() {
cin >> n >> m;
upp(i, 1, n) cin >> a[i], sum[i] = (sum[i - 1] + a[i]) % X;
INV1 = qmi(2, X - 2, X);
t1.init(1);
t2.init(0);
t1.build(1, 1, n);
t2.build(1, 1, n);
upp(i, 1, m) {
int op, l, r;
cin >> op >> l >> r;
if (op == 1) {
t1.change(1, l, r);
t2.change(1, l, r);
} else {
int x = t1.qry(1, l, r), y = t2.qry(1, l, r);
cout << (((x - y) % X + X) % X * qmi(T, X - 2, X) % X +
(sum[r] - sum[l - 1] + X) % X) %
X
<< endl;
}
}
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int tt = 1;
while (tt--) solve();
return 0;
}
[国家集训队] 等差子序列
给定你一个长度为 \(n\) 的排列 \(a\),求是否存在 \(1\le x<y<z\le n\),使得 \(a_{x},a_{y},a_{z}\) 构成一个等差数列。
\(n\le 5\times 10^5\)。
考虑枚举 \(y\),以及公差 \(t\)。
那么就有 \(a_{y}+t,a_{y}-t\) 在 \(y\) 的左右边出现。
我们考虑一个数组 \(b\),如果 \(a_{i}\) 在 \(y\) 的左边,\(b_{i}=1\),否则为 \(0\)。
则原命题变换为,枚举 \(y\),存在公差 \(t\),使得 \(b_{a_{y}-t}\ne b_{a_{y}+t}\)。
考虑这个命题的否命题,枚举 \(y\),对于任意的公差 \(t\),都有 \(b_{a_{y}-t}=b_{a_{y}+t}\)。
此时可以发现,如果对于一个 \(y\) 来说,满足否命题,就意味着数组 \(b\) 以 \(y\) 为中心是一个回文串。
现在问题就变成了,有一个 \(01\) 序列,每次操作询问两个一正一反长度一样的串是否相等,或者修改一个位置上的值(枚举 \(y\) 的所需改变)。
一般而言,检验串相等的方法是哈希,我们也考虑哈希。
于是我们只需要用线段树维护区间哈希值即可。
美味
给定序列长度为 \(n\) 的序列 \(a\),\(q\) 次询问每次给出四元组 \((l,r,x,k)\)。
求 \(\max_{l\le i\le r}\{k\oplus (a_{i}+x)\}\)。
\(1\le n,q \le 10^5\)。
首先考虑 \(l=1\) 的时候,我们按照 \(r\) 从小到大,离线处理询问。再考虑我们的 \(x=0\),开值域线段树,那么这个问题就变成了在线段树上贪心跳节点的存在性问题,相当于查看 \([0,2^{k-1}-1]\) 等区间是否存在数。然后对于 \(x\ne 0\),我们只需要把区间的端点减去 \(x\),再用 \(O(\log V)\) 的区间查询即可,这样是 \(O(\log^2 V)\) 的。然后 \(l\ne 1\),我们可以维护区间最左边不为 \(0\) 的数的位置,查看是否 \(\ge l\) 即可判断存在性。

浙公网安备 33010602011771号