P4433 [COCI2009-2010#1] ALADIN 题解
Problem
有一个长度为 \(n\) 初始所有元素均为为 \(0\) 的数列 \(a\)。
要求支持两种操作:
-
给定 \(L, R, A, B\),表示对于 \(\forall i\in [L, R]\),\(a_i \gets (A\times (i - L + 1)) \bmod B\)。
-
给定 \(L, R\),输出 \(\sum\limits_{i = L}^{R} a_i\)。
\(1\le n\le 10^9, 1\le q\le 5\times 10^4\)
\(8s, 64MB\)
保证任何时候 \(\sum\limits_{i = 1}^{n} a_i < 2^{63} - 1\)?
Sol
发现这个东西和等差数列相似,易于下传标记,考虑用线段树维护。
发现模数不固定,就不能直接加一个模数的 tag。于是考虑拆开修改的贡献,有一个常用的模数相关的 trick,就是 \(A\bmod B = A - \lfloor\frac{A}{B}\rfloor\times B\)。思考操作 \(L, R, A, B\) 会对线段树上的 \([l, r]\in [L, R]\) 的节点的 \(sum\) 会产生什么影响:\(\sum \limits_{i = L}^{R} A\times (i - L + 1) \bmod B = A\times \frac{(R - L + 1)\times (R - L + 2)}{2} - B\sum\limits_{i = L}^{R} \lfloor\frac{A(i - L + 1)}{B}\rfloor\)。前面的部分显然可以直接下传,计算。后面的部分不是很好计算,去掉 \(B\) 换一个形式,并用 \(len\) 替代 \(R - L + 1\),得到:\(\sum \limits_{i = 1}^{len} \lfloor \frac{Ai}{B} \rfloor\)。发现这就是一个类欧板子,甚至没有了板子的常数项,会类欧的可以直接跳过下面一段。
求解 \(\sum\limits_{i = 0}^{n} \lfloor\frac{Ai + C}{B}\rfloor\):
若 \(A = 0\) 是简单的,即 \((n + 1)\lfloor \frac{C}{B} \rfloor\)。
若 \(A\le B \wedge C\le B\):令 \(m = \lfloor \frac{An + C}{B}\rfloor\),\(\sum\limits_{i = 0}^{n} \lfloor\frac{Ai + C}{B}\rfloor = \sum\limits_{i = 0}^{n} \sum\limits_{j = 1}^{m}[j \le \lfloor \frac{Ai + C}{B} \rfloor]\)。其中的中括号是艾弗森括号,其满足条件值为 \(1\),否则为 \(0\)。向下取整是很麻烦的,用向下取整的相关性质去掉除号:
带入得到:\(\sum\limits_{i = 0}^{n}\sum \limits_{j = 0}^{m - 1} [\frac{Bj + B - C - 1}{A} < i]\),交换 \(i, j\) 维可以得到 \(\sum \limits_{j = 0}^{m - 1}\sum\limits_{i = 0}^{n}[\frac{Bj + B - C - 1}{A} < i]\),然后发现后面部分的贡献可以直接计算,于是可以得到 \(\sum \limits_{j = 0}^{m - 1}n - \lfloor\frac{Bj + B - C - 1}{A}\rfloor\),在化一下变成 \(mn - \sum\limits_{i = 0}^{m - 1}\lfloor \frac{Bi + B - C - 1}{A} \rfloor\)。若记 \(f(A, B, C, n)\) 表示 \(\sum\limits_{i = 0}^{n} \lfloor\frac{Ai + C}{B}\rfloor\),后面的和式显然就是 \(f(B, B - C - 1, A, m - 1)\),递归计算即可。
否则有 \(\sum\limits_{i = 0}^{n} \lfloor\frac{Ai + C}{B}\rfloor \Leftrightarrow \sum\limits_{i = 0}^{n} \lfloor\frac{(A\bmod B)i + (C \bmod B)}{B}\rfloor + \frac{n(n + 1)}{2}\lfloor \frac{A}{B}\rfloor + (n + 1) \lfloor \frac{C}{B} \rfloor\),前半部分为 \(f(A\bmod B, C\bmod B, B, n)\) 继续递归即可。
具体地,在线段树上维护形如 \((a, b, t)\) 的 tag,表示覆盖的是在当前的 \([L, R]\) 中,\(\lfloor\frac{ai}{b}\rfloor\) 的 \(i\) 是从 \(t\) 开始覆盖的,然后线段树进行区间修改和区间求和即可。
类欧的操作三的复杂度证明参考欧几里得算法,即次数为 \(\mathcal{O}(\log n)\) 的,而执行第二种操作之后再执行一次 \(a\) 就会变为 \(0\)。所以更新线段树上单个节点的复杂度为 \(\mathcal{O}(\log n)\),则总时间复杂度为 \(\mathcal{O}(q\log^2 n)\) 的,空间在离散化后可做到 \(\mathcal{O}(q)\)。
Code
#include<bits/stdc++.h>
#define ll long long
#define sz(a) ((int) (a).size())
#define vi vector < int >
#define pb emplace_back
#define pii pair < int, int >
#define fi first
#define se second
using namespace std;
int n, m, num;
int b[100010];
struct ques {
int opt, l, r, a, b;
} q[50010];
ll solve(ll a, ll b, ll c, ll n) {
if(!a)
return b / c * (n + 1);
if(a >= c || b >= c)
return n * (n + 1) / 2 * (a / c) + (b / c) * (n + 1) + solve(a % c, b % c, c, n);
ll m = (a * n + b) / c;
return m * n - solve(c, c - b - 1, a, m - 1);
}
struct Tag {
ll a, b, t;
Tag(ll _a = 0, ll _b = 0, ll _t = 0) {
a = _a, b = _b, t = _t;
}
};
struct segtree {
Tag laz[400010];
ll val[400010];
void tag(int x, ll L, ll R, ll va, ll vb, int t) {
L = b[L], R = b[R + 1] - 1;
val[x] = (R - L + 2 + 2 * t) * (R - L + 1) / 2 * va - vb * (solve(va, 0, vb, R - L + 1 + t) - solve(va, 0, vb, t));
laz[x].a = va, laz[x].b = vb, laz[x].t = t;
}
void down(int x, int L, int R) {
if(laz[x].a && laz[x].b) {
int mid = (L + R) >> 1;
tag(x << 1, L, mid, laz[x].a, laz[x].b, laz[x].t);
tag(x << 1 | 1, mid + 1, R, laz[x].a, laz[x].b, laz[x].t + b[mid + 1] - b[L]);
laz[x].a = laz[x].b = laz[x].t = 0;
}
}
void modify(int x, int L, int R, int l, int r, ll va, ll vb) {
if(l <= L && R <= r)
return tag(x, L, R, va, vb, b[L] - b[l]);
down(x, L, R);
int mid = (L + R) >> 1;
if(l <= mid)
modify(x << 1, L, mid, l, r, va, vb);
if(r > mid)
modify(x << 1 | 1, mid + 1, R, l, r, va, vb);
val[x] = val[x << 1] + val[x << 1 | 1];
}
ll query(int x, int L, int R, int l, int r) {
if(l <= L && R <= r)
return val[x];
down(x, L, R);
int mid = (L + R) >> 1; ll res = 0;
if(l <= mid)
res = query(x << 1, L, mid, l, r);
if(r > mid)
res += query(x << 1 | 1, mid + 1, R, l, r);
return res;
}
} t;
int main() {
ios :: sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n >> m;
n = 0;
for(int i = 1; i <= m; ++i) {
cin >> q[i].opt >> q[i].l >> q[i].r;
b[++n] = q[i].l;
b[++n] = ++q[i].r;
if(q[i].opt == 1)
cin >> q[i].a >> q[i].b;
}
sort(b + 1, b + n + 1);
n = unique(b + 1, b + n + 1) - b - 1;
for(int i = 1; i <= m; ++i) {
int l = q[i].l, r = q[i].r;
l = lower_bound(b + 1, b + n + 1, l) - b;
r = lower_bound(b + 1, b + n + 1, r) - b - 1;
if(q[i].opt == 1)
t.modify(1, 1, n - 1, l, r, q[i].a, q[i].b);
else
cout << t.query(1, 1, n - 1, l, r) << "\n";
}
return 0;
}