intmain(){
ios::sync_with_stdio(0);
cin.tie(0);
int n;
cin >> n;
vector<longlong> a(n);
for (int i = 0; i < n; i++)
cin >> a[i];
int B = (int) sqrt(n);
int NB = (n + B - 1) / B;
vector<longlong> add(NB);
int q = n;
while (q--) {
int type, l, r; longlong c;
cin >> type >> l >> r >> c;
if (type == 0) {
--l;
int lb = (l + B - 1) / B;
int rb = r / B;
if (lb > rb)
for (int i = l; i < r; i++) {
a[i] += c;
}
else {
for (int i = l; i < lb * B; i++)
a[i] += c;
for (int i = lb; i < rb; i++)
add[i] += c;
for (int i = rb * B; i < r; i++)
a[i] += c;
}
} else {
r--;
cout << a[r] + add[r / B] << '\n';
}
}
}
序列下标总是从 开始。
总是用左闭右开的方式表示区间。
NB 是块数。
l = ,r = 。 lb = ,rb = 。
对于区间 , 是整块, 和 是边块。
一般的,[lb, rb) 是整块,lb - 1 和 rb 是边块。
线段树:多层次分块
给 到 加
给 到 加
给 到 加
给 到 加
给 到 加
对于区间 来说,、、 是整块,但是它们的父节点不是整块(而是边块),我们把这样的整块称为极大整块。
intmain(){
ios::sync_with_stdio(0);
cin.tie(0);
int n;
cin >> n;
vector<longlong> a(2 * n);
for (int i = 0; i < n; i++)
cin >> a[i + n];
int q = n;
while (q--) {
int type, l, r; longlong c;
cin >> type >> l >> r >> c;
if (type == 0) {
l--;
l += n;
r += n;
while (l < r) {
if (l & 1) a[l++] += c;
if (r & 1) a[--r] += c;
l /= 2;
r /= 2;
}
} else {
r--;
r += n;
longlong ans = 0;
while (r > 0) {
ans += a[r];
r /= 2;
}
cout << ans << '\n';
}
}
}
intmain(){
ios::sync_with_stdio(0);
cin.tie(0);
int n; cin >> n;
vector<longlong> a(n);
for (int i = 0; i < n; i++)
cin >> a[i];
int B = (int) sqrt(n);
int NB = (n + B - 1) / B;
vector<vector<longlong>> b(NB); // b 即 sortedvector<longlong> add(NB);
auto build = [&](int i) {
int l = i * B, r = min(l + B, n);
b[i].assign(a.begin() + l, a.begin() + r);
sort(b[i].begin(), b[i].end());
};
for (int i = 0; i < NB; i++)
build(i);
int q = n;
while (q--) {
int type, l, r; longlong c;
cin >> type >> l >> r >> c;
l--;
int lb = (l + B - 1) / B;
int rb = r / B;
if (type == 0) {
if (lb > rb) {
for (int i = l; i < r; i++)
a[i] += c;
build(lb - 1);
} else {
for (int i = l; i < lb * B; i++)
a[i] += c;
for (int i = rb * B; i < r; i++)
a[i] += c;
for (int i = lb; i < rb; i++)
add[i] += c;
if (l != lb * B)
build(lb - 1);
if (r != rb * B)
build(rb);
}
} else {
c *= c;
int ans = 0;
if (lb > rb) {
for (int i = l; i < r; i++)
ans += (a[i] + add[lb - 1]) < c;
} else {
for (int i = l; i < lb * B; i++)
ans += (a[i] + add[lb - 1]) < c;
for (int i = rb * B; i < r; i++)
ans += (a[i] + add[rb]) < c;
for (int i = lb; i < rb; i++)
ans += lower_bound(b[i].begin(), b[i].end(), c - add[i]) - b[i].begin();
}
cout << ans << '\n';
}
}
}
intmain(){
ios::sync_with_stdio(0);
cin.tie(0);
int n; cin >> n;
vector<longlong> a(n);
for (int i = 0; i < n; i++)
cin >> a[i];
int B = (int) sqrt(n);
int NB = (n + B - 1) / B;
vector<vector<longlong>> b(NB);
vector<longlong> add(NB);
auto build = [&](int i) {
int l = i * B, r = min(l + B, n);
b[i].assign(a.begin() + l, a.begin() + r);
sort(b[i].begin(), b[i].end());
};
for (int i = 0; i < NB; i++)
build(i);
int q = n;
while (q--) {
int type, l, r, c;
cin >> type >> l >> r >> c;
l--;
int lb = (l + B - 1) / B;
int rb = r / B;
if (type == 0) {
if (lb > rb) {
for (int i = l; i < r; i++)
a[i] += c;
build(lb - 1);
} else {
for (int i = l; i < lb * B; i++)
a[i] += c;
for (int i = rb * B; i < r; i++)
a[i] += c;
for (int i = lb; i < rb; i++)
add[i] += c;
if (l != lb * B)
build(lb - 1);
if (r != rb * B)
build(rb);
}
} else {
longlong ans = LLONG_MIN;
if (lb > rb) {
for (int i = l; i < r; i++)
if (a[i] + add[lb - 1] < c)
ans = max(ans, a[i] + add[lb - 1]);
} else {
for (int i = l; i < lb * B; i++)
if (a[i] + add[lb - 1] < c)
ans = max(ans, a[i] + add[lb - 1]);
for (int i = rb * B; i < r; i++) {
if (a[i] + add[rb] < c)
ans = max(ans, a[i] + add[rb]);
}
for (int i = lb; i < rb; i++) {
auto it = lower_bound(b[i].begin(), b[i].end(), c - add[i]);
if (it != b[i].begin())
ans = max(ans, *prev(it) + add[i]);
}
}
if (ans == LLONG_MIN)
ans = -1;
cout << ans << '\n';
}
}
}
数列分块入门 4
给你一个长为 的整数序列 。处理 个操作,操作有两类:
0 l r c:把 每个都加上 。
1 l r c:输出 除以 的余数。保证 。
限制
的初始值,,每次操作后的 都在 int 范围内。
分块
取 。
对每个块 ,维护对块 的整体加的操作所加的数的总和 add[i]。
维护一个长为 的数组 a。
对于每个 都有 等于 a[j] + add[j / B]。
对每个块 ,维护块内元素对应的那些 a[j],维护 sum[i]。
区间加
对于整块 ,更新 add[i]。
对于边块 ,更新其中被修改的元素 对应的 a[j],更新 sum[i]。
时间:
查询区间和
对于整块 ,其中元素的和等于 sum[i] + add[i] * B。
对于边块,把涉及的元素逐个加起来。
时间:
intmain(){
ios::sync_with_stdio(0);
cin.tie(0);
int n;
cin >> n;
vector<longlong> a(n);
for (int i = 0; i < n; i++)
cin >> a[i];
int B = (int) sqrt(n);
int NB = (n + B - 1) / B;
vector<longlong> add(NB);
vector<longlong> sum(NB);
for (int i = 0; i < n; i++)
sum[i / B] += a[i];
int q = n;
while (q--) {
int type, l, r, c;
cin >> type >> l >> r >> c;
l--;
int lb = (l + B - 1) / B;
int rb = r / B;
if (type == 0) {
if (lb > rb) {
for (int i = l; i < r; i++) {
a[i] += c;
sum[lb - 1] += c;
}
} else {
for (int i = l; i < lb * B; i++) {
a[i] += c;
sum[lb - 1] += c;
}
for (int i = rb * B; i < r; i++) {
a[i] += c;
sum[rb] += c;
}
for (int i = lb; i < rb; i++)
add[i] += c;
}
} else {
longlong ans = 0;
if (lb > rb)
for (int i = l; i < r; i++)
ans += a[i] + add[lb - 1];
else {
for (int i = l; i < lb * B; i++)
ans += a[i] + add[lb - 1];
for (int i = rb * B; i < r; i++)
ans += a[i] + add[rb];
for (int i = lb; i < rb; i++)
ans += sum[i] + add[i] * B;
}
ans %= c + 1;
if (ans < 0) ans += c + 1;
cout << ans << '\n';
}
}
}
intmain(){
ios::sync_with_stdio(0);
cin.tie(0);
int n; cin >> n;
vector<longlong> add(2 * n), sum(2 * n);
for (int i = 0; i < n; i++)
cin >> sum[i + n];
for (int i = n - 1; i > 0; i--)
sum[i] = sum[2 * i] + sum[2 * i + 1];
int LOG = bit_width((unsigned) n);
auto apply = [&](int p, longlong c, int len) {
add[p] += c;
sum[p] += c * len;
};
auto update = [&](int p) {
sum[p] = sum[p * 2] + sum[p * 2 + 1];
};
auto push = [&](int p, int len) {
apply(p * 2, add[p], len / 2);
apply(p * 2 + 1, add[p], len / 2);
add[p] = 0;
};
int q = n;
while (q--) {
int type, l, r, c;
cin >> type >> l >> r >> c;
l--;
l += n; r += n;
if (type == 0) {
for (int i = LOG - 1; i >= 1; i--) {
if (l >> i << i != l) push(l >> i, 1 << i);
if (r >> i << i != r) push(r >> i, 1 << i);
}
{
int l2 = l, r2 = r;
int len = 1;
while (l < r) {
if (l & 1) apply(l++, c, len);
if (r & 1) apply(--r, c, len);
l >>= 1; r >>= 1;
len *= 2;
}
l = l2; r = r2;
}
for (int i = 1; i < LOG; i++) {
if (l >> i << i != l) update(l >> i);
if (r >> i << i != r) update(r >> i);
}
} else {
for (int i = LOG - 1; i >= 1; i--) {
if (l >> i << i != l) push(l >> i, 1 << i);
if (r >> i << i != r) push(r >> i, 1 << i);
}
longlong ans = 0;
while (l < r) {
if (l & 1) ans += sum[l++];
if (r & 1) ans += sum[--r];
l >>= 1; r >>= 1;
}
ans %= c + 1;
if (ans < 0) ans += c + 1;
cout << ans << '\n';
}
}
}
数列分块入门 5
给你一个长为 的整数序列 。处理 个操作,操作有两类:
0 l r c:把 每个都开平方。也就是把 变成 ()。
1 l r c:输出 。
限制
对 开平方,开 5 次平方之后就会得到 。
int x = INT_MAX;
while (x > 1) {
x = sqrt(x);
cout << x << '\n';
}
intmain(){
ios::sync_with_stdio(0);
cin.tie(0);
int n;
cin >> n;
vector<longlong> a(n);
for (int i = 0; i < n; i++)
cin >> a[i];
int B = (int) sqrt(n);
int NB = (n + B - 1) / B;
vector<vector<int>> big(NB);
vector<longlong> sum(NB);
for (int i = 0; i < n; i++) {
sum[i / B] += a[i];
if (a[i] > 1)
big[i / B].push_back(i);
}
auto update = [&](int i) {
int x = (int) sqrt(a[i]);
sum[i / B] += x - a[i];
a[i] = x;
};
int q = n;
while (q--) {
int type, l, r, c;
cin >> type >> l >> r >> c;
if (l > r) swap(l, r);
l--;
int lb = (l + B - 1) / B;
int rb = r / B;
if (type == 0) {
if (lb > rb)
for (int i = l; i < r; i++) update(i);
else {
for (int i = l; i < lb * B; i++) update(i);
for (int i = rb * B; i < r; i++) update(i);
for (int i = lb; i < rb; i++)
for (int j = 0; j < (int) big[i].size(); ) {
update(big[i][j]);
if (a[big[i][j]] == 1) {
swap(big[i][j], big[i].back());
big[i].pop_back();
} else {
j++;
}
}
}
} else {
longlong ans = 0;
if (lb > rb)
for (int i = l; i < r; i++) ans += a[i];
else {
for (int i = l; i < lb * B; i++) ans += a[i];
for (int i = rb * B; i < r; i++) ans += a[i];
for (int i = lb; i < rb; i++) ans += sum[i];
}
cout << ans << '\n';
}
}
}
intmain(){
ios::sync_with_stdio(0);
cin.tie(0);
int n; cin >> n;
int B = 333;
int NB = (n + B - 1) / B;
vector<vector<int>> b(NB);
for (int i = 0; i < n; i++) {
int x; cin >> x;
b[i / B].push_back(x);
}
int q = n;
while (q--) {
int type; cin >> type;
if (type == 0) {
int p, x; cin >> p >> x;
p--;
for (int i = 0; i < (int) b.size(); i++) {
if (p <= (int) b[i].size()) {
b[i].insert(b[i].begin() + p, x);
if ((int) b[i].size() == 2 * B) {
vector<int> t(b[i].begin() + B, b[i].end());
b.insert(b.begin() + i + 1, t);
b[i].resize(B);
}
break;
}
p -= (int) b[i].size();
}
} else {
int p; cin >> p;
p--;
for (int i = 0; i < (int) b.size(); i++) {
if (p < (int) b[i].size()) {
cout << b[i][p] << '\n';
break;
}
p -= (int) b[i].size();
}
}
}
}
数列分块入门 7
给你一个长为 的整数序列 。处理 个操作,操作有三类:
0 l r c:把 每个都加 。
1 l r c:把 每个都乘 。
1 l r c:询问 的值模 (忽略 和 )。
限制
, 在 int 范围内。
分块
取 。
对每个块 ,我们维护
标记:对它的整体修改操作 tag[i]。
tag[i] = ,表示对块 里的每个数先乘以 ,再加上 。
整体加 可表示为 ,整体乘 可表示为 。
先做 再做 ,合起来可表示为 。
这叫作操作的复合或合成。
另外我们维护一个数组 a,表示序列 ,但不需要实时更新。
区间修改
对于整块 ,更新 tag[i]。
对于边块 ,把 tag[i] 下传到数组 a,然后逐个修改当前块内涉及的元素。
单点查询
把 tag[i / B] 作用到 a[i],就得到当前的 。
intmain(){
ios::sync_with_stdio(0);
cin.tie(0);
constint mod = 10007;
int n;
cin >> n;
vector<int> a(n);
for (int i = 0; i < n; i++) {
cin >> a[i];
a[i] %= mod;
if (a[i] < 0) a[i] += mod;
}
structF {
int mul = 1;
int add = 0;
};
int B = (int) sqrt(n);
int NB = (n + B - 1) / B;
vector<F> lz(NB); // lz 是 lazy 的简写auto push = [&](int ib) {
for (int i = ib * B; i < min(n, (ib + 1) * B); i++)
a[i] = (a[i] * lz[ib].mul + lz[ib].add) % mod;
lz[ib] = {1, 0};
};
int q = n;
while (q--) {
int type, l, r, c;
cin >> type >> l >> r >> c;
if (type == 2) {
r--;
int i = r / B;
cout << (a[r] * lz[i].mul + lz[i].add) % mod << '\n';
} else {
l--;
int lb = (l + B - 1) / B;
int rb = r / B;
c %= mod;
if (c < 0) c += mod;
F f;
if (type == 0) f = {1, c};
else f = {c, 0};
if (lb > rb) {
push(lb - 1);
for (int i = l; i < r; i++) {
a[i] = (a[i] * f.mul + f.add) % mod;
}
} else {
if (l < lb * B) {
push(lb - 1);
for (int i = l; i < lb * B; i++)
a[i] = (a[i] * f.mul + f.add) % mod;
}
if (rb * B < r) {
push(rb);
for (int i = rb * B; i < r; i++)
a[i] = (a[i] * f.mul + f.add) % mod;
}
for (int i = lb; i < rb; i++) {
lz[i].mul = (lz[i].mul * f.mul) % mod;
lz[i].add = (lz[i].add * f.mul + f.add) % mod;
}
}
}
}
}
用线段树处理「区间修改,单点查询」
《数列分块入门 1》是「区间加,单点查询」,这题的修改操作和区间加的区别在于:
加是不讲顺序的而先乘再加是讲顺序的。
例如,“先加 3 再乘 2” 和“先乘 2 再加 3”是不一样的。
与 复合的结果是 ,而 与 复合的结果是 。
我们把不讲顺序的操作称为可交换的,讲顺序的操作称为不可交换的。
区间修改
按从上往下的顺序下传边块的标记。
把当前操作作用在极大整块上:更新它的标记。
单点查询
按从下到上的顺序把 所属的块的标记作用在 的初始值上,结果就是 的当前值。
structF {
int mul = 1;
int add = 0;
};
F composite(F x, F y){ // 先 y 后 xreturn {x.mul * y.mul % mod, (x.mul * y.add + x.add) % mod};
};
intmain(){
ios::sync_with_stdio(0);
cin.tie(0);
int n; cin >> n;
vector<int> a(n);
for (int i = 0; i < n; i++) {
cin >> a[i];
a[i] %= mod;
}
vector<F> lz(2 * n);
auto apply = [&](int i, F f) {
lz[i] = composite(f, lz[i]);
};
auto push = [&](int i) { // 下传标记apply(2 * i, lz[i]);
apply(2 * i + 1, lz[i]);
lz[i] = {1, 0};
};
int LOG = 31 - __builtin_clz(n); // log2(n) 向下取整int q = n;
while (q--) {
int type, l, r, c;
cin >> type >> l >> r >> c;
if (type == 2) {
int v = a[r - 1];
int p = r - 1 + n;
while (p > 0) {
v = (lz[p].mul * v + lz[p].add) % mod;
p /= 2;
}
if (v < 0) v += mod;
cout << v << '\n';
} else {
c %= mod;
F f;
if (type == 0) f = {1, c};
else f = {c, 0};
l--;
l += n; r += n;
for (int i = LOG; i >= 1; i--) {
if (l >> i << i != l)
push(l >> i);
if (r >> i << i != r)
push(r >> i);
}
while (l < r) {
if (l & 1) apply(l++, f);
if (r & 1) apply(--r, f);
l /= 2; r /= 2;
}
}
}
}
intmain(){
ios::sync_with_stdio(0);
cin.tie(0);
int n;
cin >> n;
vector<int> a(n);
for (int i = 0; i < n; i++)
cin >> a[i];
int B = (int) sqrt(n);
int NB = (n + B - 1) / B;
vector<bool> mark(NB);
vector<int> val(NB);
auto push = [&](int ib) {
if (mark[ib]) {
for (int i = ib * B; i < (ib + 1) * B; i++) {
a[i] = val[ib];
}
mark[ib] = false;
}
};
int q = n;
while (q--) {
int l, r, c;
cin >> l >> r >> c;
l--;
int lb = (l + B - 1) / B;
int rb = r / B;
int ans = 0;
if (lb > rb) {
push(lb - 1);
for (int i = l; i < r; i++) {
ans += a[i] == c;
a[i] = c;
}
} else {
if (l < lb * B) {
push(lb - 1);
for (int i = l; i < lb * B; i++) {
ans += a[i] == c;
a[i] = c;
}
}
if (rb * B < r) {
push(rb);
for (int i = rb * B; i < r; i++) {
ans += a[i] == c;
a[i] = c;
}
}
for (int i = lb; i < rb; i++) {
if (mark[i]) {
ans += (val[i] == c) * B;
} else {
for (int j = i * B; j < (i + 1) * B; j++)
ans += a[j] == c;
}
mark[i] = true;
val[i] = c;
}
}
cout << ans << '\n';
}
}
intmain(){
ios::sync_with_stdio(0);
cin.tie(0);
int n;
cin >> n;
map<int,int> a;
for (int i = 0; i < n; i++) {
cin >> a[i];
}
int q = n;
while (q--) {
int l, r, c;
cin >> l >> r >> c;
--l;
auto lptr = a.insert({l, prev(a.upper_bound(l))->second}).first;
auto rptr = a.insert({r, prev(a.upper_bound(r))->second}).first;
int ans = 0;
for (auto it = lptr; it != rptr; ++it) {
auto [i, v] = *it;
if (v == c) {
ans += next(it)->first - i;
}
}
a.erase(lptr, rptr);
a[l] = c;
cout << ans << '\n';
}
}
auto work0 = [&](int l, int r, int x) {
for (int i = l; i < r; i++) {
a1[i] += x;
sum1[i / B] += x;
a2[ip[i]] += x;
sum2[ip[i] / B] += x;
}
};
auto work1 = [&](int l, int r, int x) {
for (int i = l; i < r; i++) {
a2[i] += x;
sum2[i / B] += x;
a1[p[i]] += x;
sum1[p[i] / B] += x;
}
};
auto query2 = [&](int l, int r) {
longlong ans = 0;
for (int i = l; i < r; i++)
ans += a1[i] + add1[i / B];
return ans;
};
auto query3 = [&](int l, int r) {
longlong ans = 0;
for (int i = l; i < r; i++)
ans += a2[i] + add2[i / B];
return ans;
};
while (q--) {
int type, l, r, x;
cin >> type >> l >> r;
if (type < 2) cin >> x;
l--;
int lb = (l + B - 1) / B, rb = r / B;
longlong ans = 0;
if (type == 0) {
if (lb > rb) {
work0(l, r, x);
}
else {
work0(l, lb * B, x);
work0(rb * B, r, x);
for (int i = lb; i < rb; i++)
add1[i] += x;
}
} elseif (type == 1) {
if (lb > rb) {
work1(l, r, x);
} else {
work1(l, lb * B, x);
work1(rb * B, r, x);
for (int i = lb; i < rb; i++)
add2[i] += x;
}
}
elseif (type == 2) {
if (lb > rb) ans = query2(l, r);
else {
ans = query2(l, lb * B) + query2(rb * B, r);
for (int i = lb; i < rb; i++)
ans += sum1[i] + add1[i] * B;
}
for (int i = 0; i < NB; i++)
ans += add2[i] * (s[i][r] - s[i][l]);
} else {
if (lb > rb) ans += query3(l, r);
else {
ans = query3(l, lb * B) + query3(rb * B, r);
for (int i = lb; i < rb; i++)
ans += sum2[i] + add2[i] * B;
}
for (int i = 0; i < NB; i++)
ans += add1[i] * (t[i][r] - t[i][l]);
}
if (type >= 2) cout << ans << '\n';
}
}