[SHOI2017]相逢是问候(线段树+扩展欧拉定理)

Description

B 君希望以维护一个长度为 n 的数组,这个数组的下标为从 1 n 的正整数。

一共有 m 个操作,可以分为两种:

  1. ai 赋值为 cai
  2. 询问 ri=laimodp

Solution

我们发现这道题有一个地方很不好处理:

caimodp≢caimodpmodp

扩展欧拉定理

abab%ϕ(p)           gcd(a,p)=1ab                  gcd(a,p)1,b<ϕ(p)ab%ϕ(p)+ϕ(p)    gcd(a,p)1,bϕ(p)       (mod p)

根据这个公式一路递推下来,每次 ϕ(ϕ(p)) ,之后最多 log 次变换后 ϕ 值就变为了1,所以超过次数后无需修改.

因此使用线段树统计和,每次暴力修改即可.(注意还要预处理快速幂时间,是快速幂时间复杂度优化成 O(1) )

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

typedef long long ll;
const int maxn = 50005;
int n, m, p, c, a[maxn], phi[maxn], k;
int num[30][20005][2];

struct node {
    int l, r, cnt;
    ll sum;
}t[maxn * 6];

inline ll gi()
{
    char c = getchar();
    while(c < '0' || c > '9') c = getchar();
    ll sum = 0;
    while('0' <= c && c <= '9') sum = sum * 10 + c - 48, c = getchar();
    return sum;
}

inline ll get_phi(ll x)
{
    ll res = x;
    for(int i = 2; i * i <= x; ++i) 
        if(x % i == 0) {
            res = res / i * (i - 1);
            while(x % i == 0) x /= i;
        }
    if(x > 1) res = res / x * (x - 1);
    return res;
}

inline ll Pow(ll x, ll k, ll mod)
{
    ll res = 1;
    while(k) {
        if(k & 1) res = (res * x) % mod;
        x = (x * x) % mod;
        k >>= 1;
    }
    return res;
}

inline ll _pow(ll k, ll t)
{
    return (ll)num[t][k / 16384][1] * num[t][k % 16384][0] % phi[t];
}

inline ll gcd(ll a, ll b)
{
    if(!b) return a;
    return gcd(b, a % b);
}

#define mid ((t[s].l + t[s].r) >> 1)
#define lch (s << 1)
#define rch (s << 1 | 1)
void build(int s, int l, int r)
{
    t[s].l = l; t[s].r = r; t[s].cnt = 0;
    if(l == r) {t[s].sum = a[l] % phi[0]; return;}
    build(lch, l, mid);
    build(rch, mid + 1, r);
    t[s].sum = (t[s << 1].sum + t[s << 1 | 1].sum) % phi[0];
}

ll query(int s, int l, int r)
{
    if(l <= t[s].l && r >= t[s].r) return t[s].sum;
    if(r <= mid) return query(lch, l, r);
    else if(l > mid) return query(rch, l, r);
    else return (query(lch, l, r) + query(rch, l, r)) % phi[0];
}

inline ll modify(int cnt, ll num)
{
    ll res = num;
    if(res >= phi[cnt]) res = res % phi[cnt] + phi[cnt];
    for(int i = cnt; i > 0; --i) {
        res = _pow(res, i - 1);
        if(gcd(c, res) != 1) res += phi[i - 1];
    }
    return res % phi[0];
}

void change(int s, int l, int r)
{
    if(t[s].cnt >= k) return ;
    if(t[s].l == t[s].r) {
        ++t[s].cnt;
        t[s].sum = modify(t[s].cnt, a[t[s].l]);
        return;
    }
    if(r <= mid) change(lch, l, r);
    else if(l > mid) change(rch, l, r);
    else change(lch, l, r), change(rch, l, r);
    t[s].sum = (t[lch].sum + t[rch].sum) % phi[0];
    t[s].cnt = min(t[lch].cnt, t[rch].cnt);
}

void prepare()
{
    for(int i = 0; i <= k; ++i) {
        int tmp = phi[i], j = Pow(c, 16384, phi[i]);
        num[i][0][0] = num[i][0][1] = 1;
        for(int p = 1; p < 16384; ++p) {
            num[i][p][0] = (ll)num[i][p - 1][0] * c % tmp;
            num[i][p][1] = (ll)num[i][p - 1][1] * j % tmp;
        }
    }
}

int main()
{
    n = gi(); m = gi(); p = gi(); c = gi();
    for(int i = 1; i <= n; ++i) a[i] = gi();
    phi[0] = p;
    while(p != 1) {
        phi[++k] = get_phi(p);
        p = phi[k];
    }
    phi[++k] = 1;

    prepare();
    build(1, 1, n);

    for(int i = 1, opt, l, r; i <= m; ++i) {
        opt = gi(); l = gi(); r = gi();
        if(!opt) change(1, l, r);
        else printf("%lld\n", query(1, l, r));
    }

    return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值