LOJ #2142. 「SHOI2017」相逢是问候(欧拉函数 + 线段树)

题意

给出一个长度为 \(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\) 求一个幂次对于 \(p\) 模的值,可以利用大步小步预处理也就是 \(\sqrt p\) 打个表,然后最后复杂度就可以做到 \(O(\sqrt p \log^2 p + n (\log n + \log p)) \log p)\) 了。

总结

对于不断开方,或者与幂次有关的题,考虑欧拉扩展定理就行啦 qwq 因为是不超过 \(O(\log p)\) 层的。

代码

具体看看代码实现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;

}

转载于:https://www.cnblogs.com/zjp-shadow/p/9757406.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值