LOJ #2142. 「SHOI2017」相逢是問候(歐拉函數 + 線段樹)
阿新 • • 發佈:2018-10-09
getc 現在 ifd int deb lock update 序列 次數
我們發現多次操作後 \(\varphi(...\varphi(p)) = 1\) 時,最高層就 \(\bmod\) 變成 \(0\) ,然後結果以後就不會改變了。
求一個冪次對於 \(p\) 模的值,可以利用大步小步預處理也就是 \(\sqrt p\) 打個表,然後最後復雜度就可以做到 \(O(n (\log n + \log p) \log p)\) 了。
題意
給出一個長度為 \(n\) 的序列 \(\{a_i\}\) 以及一個數 \(p\) ,現在有 \(m\) 次操作,每次操作將 \([l, r]\) 區間內的 \(a_i\) 變成 \(c^{a_i}\) 。
或者詢問 \([l, r]\) 之間所有 \(a_i\) 的和對 \(p\) 取模的結果 。
\(n, m \le 5 \times 10^4, p \le 2^{14}\)
題解
考慮歐拉降冪(擴展歐拉定理),不會的可以看 這篇博客 。
然後對於這些不斷疊加的指數,有如下式子
\[
\begin{align}
& \quad \ c^{c^x} &\pmod p \\
& \equiv c^{{c^x} \bmod \varphi(p) + \varphi(p)} &\pmod p \& \equiv c^{{c^{x \bmod \varphi(\varphi(p)) + \varphi(\varphi(p)) }} \mod \varphi(p) + \varphi(p)} &\pmod p
\end{align}
\]
我們發現多次操作後 \(\varphi(...\varphi(p)) = 1\) 時,最高層就 \(\bmod\) 變成 \(0\) ,然後結果以後就不會改變了。
這個次數約是 \(O(\log p)\) 次的,我們就有個很直觀的想法。
考慮預處理每個數進行多次操作後變成的數,然後每次暴力改需要改的數。
然後對於線段樹每個區間,維護這個區間中的數改變次數的最小值,如果最小值 \(\ge\) 當前的 \(\varphi\) 的層數,直接退出即可。
至於預處理十分的麻煩,不僅快速冪需要考慮是否 \(\ge \varphi(p)\) ,而且每次乘法都需要考慮這個qwq
然後為了降低復雜度,我們在預處理的時候,對於底數 \(c\)
代碼
具體看看代碼實現qwq
#include <bits/stdc++.h> #define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i) #define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i) #define Set(a, v) memset(a, v, sizeof(a)) #define Cpy(a, b) memcpy(a, b, sizeof(a)) #define debug(x) cout << #x << ": " << (x) << endl #define DEBUG(...) fprintf(stderr, __VA_ARGS__) using namespace std; typedef long long ll; template<typename T> inline bool chkmin(T &a, T b) {return b < a ? a = b, 1 : 0;} template<typename T> inline bool chkmax(T &a, T b) {return b > a ? a = b, 1 : 0;} inline int read() { int x(0), sgn(1); char ch(getchar()); for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1; for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48); return x * sgn; } void File() { #ifdef zjp_shadow freopen ("2142.in", "r", stdin); freopen ("2142.out", "w", stdout); #endif } const int N = 5e4 + 1e3; inline int phi(int x) { int res = x; For (i, 2, sqrt(x + .5)) if (!(x % i)) { while (!(x % i)) x /= i; res = res / i * (i - 1); } if (x > 1) res = res / x * (x - 1); return res; } inline int fpm(int x, int power, int mod) { int res = 1; for (; power; power >>= 1, x = 1ll * x * x % mod + (1ll * x * x >= mod) * mod) if (power & 1) res = 1ll * res * x % mod + (1ll * res * x >= mod) * mod; return res; } int n, m, Mod, c; int a[N], Phi[33], val[N][33], lim = 0; const int Block = 1 << 14, All = Block - 1; int Pow1[N][33], Pow2[N][33]; void Math_Init() { for (Phi[0] = Mod; Phi[lim] > 1; ++ lim) Phi[lim + 1] = phi(Phi[lim]); Phi[++ lim] = 1; For (j, 1, lim) { int cur = fpm(c, Block, Phi[j]); Pow1[0][j] = Pow2[0][j] = 1; For (i, 1, Block - 1) { Pow1[i][j] = 1ll * Pow1[i - 1][j] * cur % Phi[j] + (1ll * Pow1[i - 1][j] * cur >= Phi[j]) * Phi[j]; Pow2[i][j] = 1ll * Pow2[i - 1][j] * c % Phi[j] + (1ll * Pow2[i - 1][j] * c >= Phi[j]) * Phi[j]; } } For (i, 1, n) { val[i][0] = a[i]; For (j, 1, lim) { int cur = a[i] % Phi[j] + (a[i] >= Phi[j]) * Phi[j]; Fordown (k, j - 1, 1) { cur = 1ll * Pow1[cur / Block][k] * Pow2[cur & All][k] % Phi[k] + (1ll * Pow1[cur / Block][k] * Pow2[cur & All][k] >= Phi[k]) * Phi[k]; } val[i][j] = fpm(c, cur, Mod) % Mod; } } } inline int Plus(int a, int b) { return (a += b) >= Mod ? a - Mod : a; } #define lson o << 1, l, mid #define rson o << 1 | 1, mid + 1, r template<int Maxn> struct Segment_Tree { int times[Maxn], sumv[Maxn]; inline void Push_Up(int o) { sumv[o] = Plus(sumv[o << 1], sumv[o << 1 | 1]); times[o] = min(times[o << 1], times[o << 1 | 1]); } void Build(int o, int l, int r) { if (l == r) { sumv[o] = a[l] % Mod; return ; } int mid = (l + r) >> 1; Build(lson); Build(rson); Push_Up(o); } void Update(int o, int l, int r, int ul, int ur) { if (times[o] >= lim) return ; if (ul <= l && r <= ur) ++ times[o]; if (l == r) { sumv[o] = val[l][times[o]]; return ; } int mid = (l + r) >> 1; if (ul <= mid) Update(lson, ul, ur); if (ur > mid) Update(rson, ul, ur); Push_Up(o); } int Query(int o, int l, int r, int ql, int qr) { if (ql <= l && r <= qr) return sumv[o]; int mid = (l + r) >> 1; if (qr <= mid) return Query(lson, ql, qr); if (ql > mid) return Query(rson, ql, qr); return Plus(Query(lson, ql, qr), Query(rson, ql, qr)); } }; Segment_Tree<N << 2> T; int main () { File(); n = read(); m = read(); Mod = read(); c = read(); For (i, 1, n) a[i] = read(); Math_Init(); T.Build(1, 1, n); For (i, 1, m) { int opt = read(), l = read(), r = read(); if (!opt) T.Update(1, 1, n, l, r); else printf ("%d\n", T.Query(1, 1, n, l, r)); } return 0; }
LOJ #2142. 「SHOI2017」相逢是問候(歐拉函數 + 線段樹)