題解 [六省聯考2017] 相逢是問候
阿新 • • 發佈:2021-11-10
題目連結
題目描述
維護一個序列,支援區間求和,以及將區間內每一個數 \(a_i\) 變成 \(c^{a_i}\) (c為給定的常數), 並要求結果對 \(p\) 取模 ( \(p\) 不一定是質數 )
數列元素個數 \(n \leq 10^5\), 操作次數 \(m \leq 10^5\), \(1 \leq c < p \leq 10^8\)
注 : 以下稱 “將區間內每一個數 \(a_i\) 變成 \(c^{a_i}\)” 為 “操作”
分析
一道看著就很線段樹的題目 ...
首先我們發現區間和似乎並不是那麼好維護:假定我們維護了 \(a_i\) 的區間和,但我們並不能很快的推出 \(c^{a_i}\)
但除了線段樹外,我們應該還會很自然的想到擴充套件尤拉定理 \[a^b \equiv a^{(b\bmod\varphi(p)) + \varphi(p) } \pmod p, b \ge \varphi(p) \]
那麼很顯然的,當 \(a_i\) 對應的模數 \(\varphi(\varphi(\varphi(\varphi(...p)))) = 1\) 時,不論再怎麼操作 \(a_i\) 也不會變化了
而對一個數 \(p\) 不停地取 \(\varphi\)
那麼對於序列中所有的數,他們的總操作次數就應該不超過 \(n \log p\).
也就是說,對於每一個區間操作,我們只需要操作其中沒有操作到 \(\log p\) 次的數, 其他的數可以不管他.
那顯然可以考慮預處理出每個數在前 \(\log p\) 次操作下分別會變成多少,操作時單點修改即可,複雜度就是 \(O(n\log n\log p)\).
這樣做還有個的好處:我們可以用樹狀陣列代替掉線段樹.
至於如何在區間操作中快速找到哪些數的操做次數小於 \(\log p\), 我們可以考慮使用逐漸變形成並查集的單向連結串列,具體而言就是,我們有一個 \(next\)
顯然初始時 \(next_i = i + 1\).
那麼查詢下標大於等於 i 的第一個操作次數小於 \(\log p\) 的數可以這樣實現:
int get(int i) {
if (time[i] < logp)
return i;
else
return next[i] = get(next[i]);
}
這應該比較直觀的體現了他為什麼是"逐漸變形成並查集的單向連結串列"......
程式碼
#include <bits/stdc++.h>
#define lowbit(x) x&-x
using namespace std;
typedef long long LL;
const int N = 50010;
const int lam = 10;
LL n, m, p, c, logcp, top, nxt[N], tree[N], a[N], tms[N], phi[N], val[lam + 10][N];
inline int read()
{
int x = 0, f = 1;
char c = getchar();
while(c < '0' || c > '9') { if(c == '-') f = -1; c = getchar(); }
while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return x * f;
}
LL ksm(LL x, LL p)
{
LL cur = c, res = 1;
for(; x; x >>= 1, cur = cur * cur % p) if(x & 1) res = res * cur % p;
return res;
}
int get_phi(int x) //暴力求尤拉函式
{
LL res = 1;
for(int i = 2; i * i <= x; i++){
if(x % i == 0){
res *= (i - 1), x /= i;
while(x % i == 0) res *= i, x /= i;
}
}
return (x == 1) ? res : res * (x - 1);
}
int dfs(int x, int num, int t)//深搜擴充套件尤拉定理
{
if(phi[t] == 1) return 1;
if(!num) return a[x];
if(num < top) return val[num][x]; //特判不能用擴充套件尤拉定理的部分
return ksm(dfs(x, num - 1, t + 1), phi[t]) + phi[t];
}
void prework()//預處理出a[i]操作b次後會變成多少,即val[b][i]
{
LL o = 1;
if(c == 1) logcp = 1e9;
else while(o < p) logcp++, o = o * c;
//logcp 就是以c為底p的對數
int cur = p; phi[0] = p;
for(int i = 1; i <= lam; i++){
int now = get_phi(cur);
phi[i] = cur = now;
}
for(int j = 1; j <= n; j++){
val[0][j] = a[j];
bool flag = true;
for(int i = 1; i <= lam; i++)
if(val[i - 1][j] < logcp && flag) val[i][j] = ksm(val[i - 1][j], p); //特判不能用擴充套件尤拉定理的部分
else{
if(flag) flag = false, top = i;
val[i][j] = dfs(j, i, 0) % p;
}
}
}
void add(int pos, int val)
{
for(; pos < N; pos += lowbit(pos))
tree[pos] = (tree[pos] + val + p) % p;
}
LL que(int pos)
{
LL res = 0;
for(; pos; pos -= lowbit(pos)) res = (res + tree[pos]) % p;
return res;
}
int get(int now)
{
if(tms[now] < lam) return now;
else return nxt[now] = get(nxt[now]);
}
int main()
{
n = read(), m = read(), p = read(), c = read();
for(int i = 1; i <= n; i++) a[i] = read(), nxt[i] = i + 1;
for(int i = 1; i <= n; i++) add(i, a[i]);
prework();
for(int i = 1, op, l, r; i <= m; i++){
op = read(), l = read(), r = read();
if(op) printf("%lld\n", (que(r) - que(l - 1) + p) % p);
else{
int cur = get(l);
while(cur <= r){
LL x = val[tms[cur] + 1][cur], de = x - a[cur];
add(cur, de);
a[cur] = x, tms[cur] ++, cur = get(nxt[cur]);
}
}
}
return 0;
}
順便一提,雖然說理論上每個數需要操作 \(\log p\) 次才不會變化,但這題資料比較水,每個數操作 5 次就可以不管他了.
還有就是一定要注意擴充套件尤拉定理需要滿足指數不小於 \(\varphi(p)\).