题解 [六省联考2017] 相逢是问候

题目链接

Luogu
Loj

题目描述

维护一个序列,支持区间求和,以及将区间内每一个数 \(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_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\) ,在取了 \(\log p\) 次后这个数必然会变成 \(1\).

那么对于序列中所有的数,他们的总操作次数就应该不超过 \(n \log p\).

也就是说,对于每一个区间操作,我们只需要操作其中没有操作到 \(\log p\) 次的数, 其他的数可以不管他.

那显然可以考虑预处理出每个数在前 \(\log p\) 次操作下分别会变成多少,操作时单点修改即可,复杂度就是 \(O(n\log n\log p)\).

这样做还有个的好处:我们可以用树状数组代替掉线段树.

至于如何在区间操作中快速找到哪些数的操做次数小于 \(\log p\), 我们可以考虑使用逐渐变形成并查集的单向链表(我自己瞎扯的名字)

具体而言就是,我们有一个 \(next\) 数组, \(next_i\) 表示下标大于等于 i 的数中第一个操作次数小于 \(\log p\) 的数 (即继续操作可能会改变数值的数) 的下标,并有一个 \(time\) 数组记录每个数当前的操作次数.

显然初始时 \(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)\).

posted @ 2021-11-10 15:36  sgweo8ys  阅读(58)  评论(0)    收藏  举报