D. Interval Cubing

D. Interval Cubing 

While learning Computational Geometry, Tiny is simultaneously learning a useful data structure called segment tree or interval tree. He has scarcely grasped it when comes out a strange problem:

Given an integer sequence $a_1,a_2,\ldots,a_n$. You should run q queries of two types:

  1. Given two integers $l$ and $r$ $(1 \leq l \leq r \leq n)$, ask the sum of all elements in the sequence $a_l,a_{l + 1},\ldots,a_r$.
  2. Given two integers $l$ and $r$ $(1 \leq l \leq r \leq n)$, let each element $x$ in the sequence $a_l,a_{l + 1},\ldots,a_r$ becomes $x^3$. In other words, apply an assignments $a_l=a_l^3,a_{l + 1}=a_{l + 1}^3,\ldots,a_r=a_r^3$.

For every query of type 1, output the answer to it.

Tiny himself surely cannot work it out, so he asks you for help. In addition, Tiny is a prime lover. He tells you that because the answer may be too huge, you should only output it modulo $95542721$ (this number is a prime number).

Input

The first line contains an integer $n$ $(1 \leq n \leq 10^5)$, representing the length of the sequence. The second line contains n space-separated integers $a_1,a_2,\ldots,a_n$ $(0 \leq a_i \leq 10^9)$.

The third line contains an integer $q$ $(1 \leq q \leq 10^5)$, representing the number of queries. Then follow $q$ lines. Each line contains three integers $t_i$ $(1 \leq t_i \leq 2)$, $l_i$, $r_i$ $(1 \leq l_i \leq r_i \leq n)$, where $t_i$ stands for the type of the query while $l_i$ and $r_i$ is the parameters of the query, correspondingly.

Output

For each 1-type query, print the answer to it per line.

You should notice that each printed number should be non-negative and less than $95542721$.

Examples

Input

8
1 2 3 4 5 6 7 8
5
1 2 5
2 2 5
1 2 5
2 3 6
1 4 7

Output

14
224
2215492

 

解题思路

  在对一个数 $x$ 执行 $k$ 次立方操作后,其结果为 $x^{3^k}$。在这种幂次形式下,当指数很大而底数与模数互质时,通常会使用欧拉定理进行降幂。根据欧拉定理,有 $x^{\varphi(m)} \equiv 1 \pmod{m}$,其中 $\gcd(x,m)=1$。因此,$x^{3^k} \equiv x^{3^k \bmod \varphi(m)} \pmod{m}$。由于 $m$ 是质数,有 $\varphi(m) = m-1$,从而可以简化为 $x^{3^k} \equiv x^{3^k \bmod (m-1)} \pmod{m}$。

  注意到指数部分的 $3^k \bmod (m-1)$ 仍是幂次的形式,因此我们可以继续对其进行欧拉降幂(前提是 $\gcd(3,m-1)=1$)。根据欧拉定理,$3^k \equiv 3^{\varphi(m-1)} \pmod{m-1}$。因此,最终我们得到 $x^{3^k} \equiv x^{3^{k \bmod \varphi(m-1)}} \pmod{m}$。然而 $\varphi(m-1) = \varphi(95542721-1) = 28311552$,而操作次数 $q$ 远小于 $\varphi(m-1)$,因此该降幂操作并没有太大意义。

  在计算 $3^k \bmod (m-1)$ 时,我们希望找到一个正整数 $r$,使得 $3^r \equiv 1 \pmod{m-1}$,从而能够简化为 $3^k \equiv 3^{k \bmod r} \pmod{m-1}$。根据欧拉定理,选择 $r = \varphi(m-1)$ 是一个可行的方案,但不一定是满足条件的最小值。我们实际上希望找到最小的 $r \in \mathbb{N}^+$,使得 $3^r \equiv 1 \pmod{m-1}$。这里介绍两种不同的方法找到这个最小的 $r$。更一般地,问题转化为求解 $a^{r} \equiv 1 \pmod{m}$,其中 $\gcd(a,m)=1$,刚好最近 lc 有出类似的题目:Smallest All-Ones Multiple

  方法一,直接从 $1$ 到 $m$ 暴力枚举 $r$,找到满足 $a^{r} \equiv 1 \pmod{m}$ 的最小的 $r$。这样可行是因为根据鸽巢原理,$a^r \bmod m$ 的结果最多只有 $m$ 种可能,当 $r$ 超过 $m$时,$a^r \bmod m$ 会出现重复。该方法时间复杂度为 $O(m)$。

  方法二,利用结论,最小的 $r$ 一定是 $\varphi(m)$ 的约数,证明可参考最幸运的数字 。对此先求出 $\varphi(m)$,然后再求出 $\varphi(m)$ 的所有约数,并找出满足 $a^{r} \equiv 1 \pmod{m}$ 的最小的 $r$。该方法的时间复杂度为 $O(\sqrt{m} \log{m})$。

  在这个本问题中,$3^r \equiv 1 \pmod{m-1}$ 的最小的 $r$ 是 $48$,其中用方法二求解的代码如下。

方法二
#include <bits/stdc++.h>
using namespace std;

typedef long long LL;

int phi(int x) {
    int ret = x;
    for (int i = 2; i * i <= x; i++) {
        if (x % i == 0) {
            ret -= ret / i;
            while (x % i == 0) {
                x /= i;
            }
        }
    }
    if (x > 1) ret -= ret / x;
    return ret;
}

int qmi(int a, int k, int m) {
    int ret = 1;
    while (k) {
        if (k & 1) ret = 1ll * ret * a % m;
        a = 1ll * a * a % m;
        k >>= 1;
    }
    return ret;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int a = 3, m = 95542721 - 1;
    int phi_m = phi(m), ret = phi_m;
    for (int i = 1; i * i <= phi_m; i++) {
        if (phi_m % i) continue;
        if (qmi(a, i, m) == 1) ret = min(ret, i);
        if (qmi(a, phi_m / i, m) == 1) ret = min(ret, phi_m / i);
    }
    cout << ret;
    
    return 0;
}

  每个 $x$ 执行 $48$ 次方操作后,结果是相同的。因此,我们只需关注每个 $a_i$ 的 $a_i^{3^0}, a_i^{3^1}, \ldots, a_i^{3^{47}}$ 的结果。

  可以用线段树来维护区间元素的和。具体的,每个线段树节点维护一个大小为 $48$ 的数组(下标从 $0$ 开始),该数组的第 $k$ 个元素表示该节点维护的区间中的元素(更新后)关于 $x^{3^k}$ 的和。在更新时,当节点对应的整个区间需要执行 $c$ 次立方操作时,我们只需将数组循环左移 $c$ 个单位即可。而每次合并更新时,只需将左右子节点的每个数组元素相加即可。

  AC 代码如下,时间复杂度为 $O(48\,n + 48\,q \log{n})$:

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;

const int N = 1e5 + 5, mod = 95542721;

int a[N];
struct Node {
    int l, r, add;
    array<int, 48> s;
}tr[N * 4];

array<int, 48> operator+(array<int, 48> &a, array<int, 48> &b) {
    array<int, 48> c;
    for (int i = 0; i < 48; i++) {
        c[i] = (a[i] + b[i]) % mod;
    }
    return c;
}

Node pushup(Node l, Node r) {
    return {l.l, r.r, 0, l.s + r.s};
}

void build(int u, int l, int r) {
    tr[u] = {l, r};
    if (l == r) {
        for (int i = 0; i < 48; i++) {
            tr[u].s[i] = a[l];
            a[l] = 1ll * a[l] * a[l] % mod * a[l] % mod;
        }
    }
    else {
        int mid = l + r >> 1;
        build(u << 1, l, mid);
        build(u << 1 | 1, mid + 1, r);
        tr[u] = pushup(tr[u << 1], tr[u << 1 | 1]);
    }
}

void upd(int u, int c) {
    rotate(tr[u].s.begin(), tr[u].s.begin() + c, tr[u].s.end());
    tr[u].add = (tr[u].add + c) % 48;
}

void pushdown(int u) {
    if (!tr[u].add) return;
    upd(u << 1, tr[u].add);
    upd(u << 1 | 1, tr[u].add);
    tr[u].add = 0;
}

void modify(int u, int l, int r) {
    if (tr[u].l >= l && tr[u].r <= r) {
        upd(u, 1);
    }
    else {
        pushdown(u);
        int mid = tr[u].l + tr[u].r >> 1;
        if (l <= mid) modify(u << 1, l, r);
        if (r >= mid + 1) modify(u << 1 | 1, l, r);
        tr[u] = pushup(tr[u << 1], tr[u << 1 | 1]);
    }
}

Node query(int u, int l, int r) {
    if (tr[u].l >= l && tr[u].r <= r) return tr[u];
    pushdown(u);
    int mid = tr[u].l + tr[u].r >> 1;
    if (r <= mid) return query(u << 1, l, r);
    if (l >= mid + 1) return query(u << 1 | 1, l, r);
    return pushup(query(u << 1, l, r), query(u << 1 | 1, l, r));
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n, m;
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    build(1, 1, n);
    cin >> m;
    while (m--) {
        int op, l, r;
        cin >> op >> l >> r;
        if (op == 1) cout << query(1, l, r).s[0] << '\n';
        else modify(1, l, r);
    }
    
    return 0;
}

 

参考资料

  Codeforces Round #185 Editorial (Div.1 A&D):https://codeforces.com/blog/entry/7787

posted @ 2026-01-02 23:25  onlyblues  阅读(3)  评论(0)    收藏  举报
Web Analytics