牛客练习赛22 E 树状数组 + DFS + 拓展欧几里德定理

题目链接


题意:
给定一个长度为 n n n的序列,进行 m m m次操作,操作有两类:
1 1 1 L L L R R R v v v : 区间 [ L , R ] [L, R] [L,R]的每个数加上 v v v
2 2 2 L L L R R R p p p : 查询 a [ L ] a [ L + 1 ] a [ L + 2 ] . . . a [ R ]   m o d   p a[L] ^ {a[L+1] ^ {a[L + 2] ^{...^{a[R]}}}} \ mod \ p a[L]a[L+1]a[L+2]...a[R] mod p


思路:

利用拓展欧几里德定理:
a x   m o d   p = a x   m o d   φ ( p ) + ( x > φ ( p ) ? φ ( p ) : 0 )   m o d   p a^x \ mod \ p = a ^{x \ mod \ \varphi(p) + (x > \varphi(p)?\varphi(p):0 ) } \ mod \ p ax mod p=ax mod φ(p)+(x>φ(p)?φ(p):0) mod p

因为一个数 p p p只需要在 l o g p log p logp次取欧拉函数以后就会变成1,而任何数 m o d 1 mod 1 mod1都是0,故实际上只需要计算前 l o g p log p logp个数带入表达式的值,利用递归即可求解。


代码:

#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<cstdio>
using namespace std;
typedef long long ll;

const int A = 1e6 + 10;
const int B = 2e7 + 10;
ll Tree[A];
int pri[B], phi[B], tot, n, m;
bool vis[B];

void Init(){
    tot = 0;phi[1] = 1;
    for (int i = 2; i < B; i++) {
        if (!vis[i]) {pri[++tot] = i; phi[i] = i-1;}
        for (int j = 1; j <= tot && i * pri[j] < B; j++) {
            vis[i * pri[j]] = 1;
            if (i % pri[j] == 0) {
                phi[i * pri[j]] = pri[j] * phi[i];break;
            }
            phi[i * pri[j]] = phi[pri[j]] * phi[i];
        }
    }
}

ll calc(ll x, ll mod){
    return x > mod?x%mod+mod:x;
}

void update(int x, int v){
    for (int i = x; i <= n; i += (i & (-i))) Tree[i] += v;
}

ll get_sum(int x) {
    ll res = 0;
    for (int i = x; i > 0; i -= (i & (-i))) {
        res += Tree[i];
    }
    return res;
}

ll fast_mod(ll n, ll m, int mod){
    ll res = 1;
    n = calc(n, mod);
    while(m > 0){
        if (m & 1) res = calc(res * n, mod);
        n = calc(n*n, mod);
        m >>= 1;
    }
    return res;
}

ll dfs(int l, int r, int mod){
    if (l == r || mod == 1) return calc(get_sum(l), mod);
    return fast_mod(get_sum(l), dfs(l + 1, r, phi[mod]), mod);
}

int main(){
    Init();
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) {
        int x;
        scanf("%d", &x);
        update(i,x);update(i+1,-x);
    }
    for (int i = 1; i <= m; i++) {
        int op, l, r, v;
        scanf("%d%d%d%d", &op, &l, &r, &v);
        if(op == 1){
            update(l,v);update(r + 1, -v);
        } else {
            printf("%lld\n", dfs(l, r, v) % v);
        }
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值