P3747 [六省联考 2017] 相逢是问候
[六省联考 2017] 相逢是问候
题目描述
Informatik verbindet dich und mich.
信息将你我连结。
B 君希望以维护一个长度为 \(n\) 的数组,这个数组的下标为从 \(1\) 到 \(n\) 的正整数。
一共有 \(m\) 个操作,可以分为两种:
-
0 l r
表示将第 \(l\) 个到第 \(r\) 个数( \(a_l,a_{l+1} ...a_r\))中的每一个数 \(a_i\) 替换为 \(c^{a_i}\),即 \(c\) 的 \(a_i\) 次方,其中 \(c\) 是输入的一个常数,也就是执行赋值 \(a_i = c^{a_i}\)。 -
1 l r
求第 \(l\) 个到第 \(r\) 个数的和,也就是输出: \(\sum_{i=l}^{r}a_i\)
因为这个结果可能会很大,所以你只需要输出结果 \(\bmod \space p\) 的值即可。
输入格式
第一行有四个整数 \(n, m, p, c\),所有整数含义见问题描述。
接下来一行 \(n\) 个整数,表示 \(a\) 数组的初始值。
接下来 \(m\) 行,每行三个整数,其中第一个整数表示了操作的类型。
- 如果是 \(0\) 的话,表示这是一个修改操作,操作的参数为 \(l, r\)。
- 如果是 \(1\) 的话,表示这是一个询问操作,操作的参数为 \(l, r\)。
输出格式
对于每个询问操作,输出一行,包括一个整数表示答案 \(\bmod \space p\) 的值。
样例 #1
样例输入 #1
4 4 7 2
1 2 3 4
0 1 4
1 2 4
0 1 4
1 1 3
样例输出 #1
0
3
样例 #2
样例输入 #2
1 40 19910626 2
0
0 1 1
1 1 1
0 1 1
1 1 1
0 1 1
1 1 1
0 1 1
1 1 1
0 1 1
1 1 1
0 1 1
1 1 1
0 1 1
1 1 1
0 1 1
1 1 1
0 1 1
1 1 1
0 1 1
1 1 1
0 1 1
1 1 1
0 1 1
1 1 1
0 1 1
1 1 1
0 1 1
1 1 1
0 1 1
1 1 1
0 1 1
1 1 1
0 1 1
1 1 1
0 1 1
1 1 1
0 1 1
1 1 1
0 1 1
1 1 1
样例输出 #2
1
2
4
16
65536
11418102
18325590
13700558
13700558
13700558
13700558
13700558
13700558
13700558
13700558
13700558
13700558
13700558
13700558
13700558
提示
【数据范围】
对于 \(0\%\) 的测试点,和样例一模一样;
对于另外 \(10\%\) 的测试点,没有修改;
对于另外 \(20\%\) 的测试点,每次修改操作只会修改一个位置(也就是 \(l = r\) ),并且每个位置至多被修改一次;
对于另外 \(10\%\) 的测试点, \(p = 2\);
对于另外 \(10\%\) 的测试点, \(p = 3\);
对于另外 \(10\%\) 的测试点, \(p = 4\);
对于另外 \(20\%\) 的测试点, \(1\le n,m \le 100\);
对于 \(100\%\) 的测试点, \(1\le n,m \le 5\times 10^4\),\(1 \le p \le 10^8\),\(0< c < p\),\(0 \le a_i < p\)。
Solution
首先对于题目中的修改操作,肯定是不能使用常规维护 tag
的方式进行处理的,因此尝试发现一下特殊性质。观察样例二可以发现,在进行了一定次数修改过后,\(a_i\) 的值会变成定值,因此考虑用类似区间开方的操作来解决。
回来看 \(c^a\) 这一操作。若干次操作如果同时作用于同一个 \(a_i\) 上,那么整个的式子就会变成类似这样的幂塔:\(c^{c^{.^{.^{.^{a}}}}}\)。定义 \(f(i)=c^{f(i-1)}\bmod p,f(0)=a\),那么可以对 \(f(i)\) 应用欧拉降幂:\(f(i)=c^{f(i-1)\bmod \varphi(p)+\varphi(p)}\bmod p\),然后对于这个指数可以进行递归处理,每递归一次 \(p\) 都会变成 \(\varphi(p)\),因此这样最多递归 \(\mathcal O(\log p)\) 次 \(p\) 就会变成 \(1\),此时无论 \(a\) 是什么值都不会影响结果了。
考虑将这个结论运用到此题上来,因为证明出来了一个数最多只会变 \(\mathcal O(\log p)\) 次,因此可以用与区间开方完全一样的做法进行解决。使用分块以及快速幂计算,时间复杂度 \(\mathcal O(n\sqrt{n}\log^2 p)\),可以拿到 90pts,考虑优化。
发现每次进行快速幂的时候的底数都不变,为 \(c\),因此考虑对于每一个模数都预处理出来其对应的光速幂数组,这样时间复杂度就变为了 \(\mathcal O(n\sqrt{n\log p})\),块长取 \(\sqrt{\dfrac{n}{\log_2n}}\) 最优。
代码写分块的话细节很少,注意判断光速幂的过程中是否出现了指数 \(\ge p\) 的情况,方便计算的时候进行欧拉降幂。
#include<bits/stdc++.h>
using namespace std;
constexpr int _N = 5e4 + 5, _LEN = 250 + 5, _S = 1e4, _MS = _S + 5;
int n, m, p, c, ori[_N], a[_N], tim[_N];
map<int, int> phi, id;
int len, cnt, bl[_N], pos[_N], br[_N], sum[_N];
bool tag[_N];
int Getphi(int x) {
int res = x;
for (int i = 2; i * i <= x; ++i) {
if (x % i) continue;
res = res / i * (i - 1);
while (!(x % i)) x /= i;
}
if (x > 1) res = res / x * (x - 1);
return res;
}
int pw[_LEN][_MS][2], tP = 0;
bool flag[_LEN][_MS][2];
void Init(int P) {
int x = id[P] = ++tP;
pw[x][0][0] = pw[x][0][1] = 1;
for (int i = 1; i <= _S; ++i) {
flag[x][i][0] = flag[x][i - 1][0] || (1ll * pw[x][i - 1][0] * c >= P);
pw[x][i][0] = 1ll * pw[x][i - 1][0] * c % P;
}
for (int i = 1; i <= _S; ++i) {
flag[x][i][1] = flag[x][i - 1][1] || (1ll * pw[x][i - 1][1] * pw[x][i - 1][_S] >= P);
pw[x][i][1] = 1ll * pw[x][i - 1][1] * pw[x][_S][0] % P;
}
}
inline pair<int, bool> Lpow(int x, int P) {
int i = id[P];
int fir = 1ll * pw[i][x % _S][0] * pw[i][x / _S][1] % P;
int sec = flag[i][x % _S][0] || flag[i][x / _S][1] || (1ll * pw[i][x % _S][0] * pw[i][x / _S][1] >= P);
return {fir, sec};
}
inline pair<int, bool> GetAns(int x, int p, int tim) {
if (!tim) return {x % p, x >= p};
if (p == 1) return {0, 1};
auto tmp = GetAns(x, phi[p], tim - 1);
int b = tmp.first + tmp.second * phi[p];
return Lpow(b, p);
}
void Change(int i) {
(sum[pos[i]] += p - a[i]) %= p, ++tim[i];
a[i] = GetAns(ori[i], p, tim[i]).first;
(sum[pos[i]] += a[i]) %= p;
}
void Modify(int l, int r) {
if (pos[l] == pos[r])
for (int i = l; i <= r; ++i) Change(i);
else {
for (int i = l; i <= br[pos[l]]; ++i) Change(i);
for (int i = pos[l] + 1; i < pos[r]; ++i) {
if (tag[i]) continue; tag[i] = 1;
for (int j = bl[i]; j <= br[i]; ++j) {
int tmp = a[j]; Change(j);
if (a[j] != tmp) tag[i] = 0;
}
}
for (int i = bl[pos[r]]; i <= r; ++i) Change(i);
}
}
int Query(int l, int r) {
int res = 0;
if (pos[l] == pos[r])
for (int i = l; i <= r; ++i) (res += a[i]) %= p;
else {
for (int i = l; i <= br[pos[l]]; ++i) (res += a[i]) %= p;
for (int i = pos[l] + 1; i < pos[r]; ++i) (res += sum[i]) %= p;
for (int i = bl[pos[r]]; i <= r; ++i) (res += a[i]) %= p;
}
return res;
}
signed main() {
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
cin >> n >> m >> p >> c;
if (n == 1) len = 1;
else len = sqrt(n / log2(n));
cnt = (n - 1) / len + 1;
for (int i = p; i > 1; i = phi[i]) phi[i] = Getphi(i), Init(i);
Init(1);
for (int i = 1; i <= n; ++i) cin >> a[i], ori[i] = a[i];
for (int i = 1; i <= cnt; ++i) {
bl[i] = (i - 1) * len + 1;
br[i] = min(n, i * len);
}
for (int i = 1; i <= cnt; ++i)
for (int j = bl[i]; j <= br[i]; ++j)
pos[j] = i, (sum[i] += a[j]) %= p;
for (int i = 1, opt, l, r; i <= m; ++i) {
cin >> opt >> l >> r;
if (opt) cout << Query(l, r) << '\n';
else Modify(l, r);
}
}