「BZOJ4869」「SHOI2017」相逢是问候

本文介绍了一种涉及数组操作的问题及其高效的解决方法。通过利用欧拉降幂公式和线段树等数据结构,实现对数组元素的快速修改与查询。文章详细解释了算法原理,并提供了具体的代码实现。

题目描述

Informatik verbindet dich und mich.
信息将你我连结。

B 君希望以维护一个长度为 n n 的数组,这 个数组的下标为从 1 1 n n 的正整数。
一共有 m m 个操作,可以分为两种:

  • 0 l r :表示将第 l l 个到第 r r 个数 {al,al+1,...,ar} { a l , a l + 1 , . . . , a r } 中的每一个数 ai a i 替换为 cai c a i ,即 c c ai a i 次方,其中 c c 是输入的一个常数,也就是执行赋值 aicai a i ← c a i
  • 1 l r :求第 l l 个到第 r r 个数的和,也就是输出:
    i=lrai ∑ i = l r a i

    因为这个结果可能会很大,所以你只需要输出结果 mod p mod   p 的值即可。

输入格式

第一行有四个整数 n,m,p,c n , m , p , c 所有整数含义见问题描述。
接下来一行 n n 个整数,表示 a a 数组的初始值。
接下来 m m 行,每行三个整数,其中第一个整数表示了操作的类型。

如果是 0 0 的话,表示这是一个修改操作,操作的参数为 l,r l , r
如果是 1 1 的话,表示这是一个询问操作,操作的参数为 l,r l , r

输出格式

对于每个询问操作,输出一行,包括一个整数表示答案 mod p mod   p 的值。

样例

样例输入 1
4 4 7 2
1 2 3 4
0 1 4
1 2 4
0 1 4
1 1 3
样例输出 1
0
3
样例输入 2
1 40 19910626 2
0
0 1 1
1 1 1
0 1 1
1 1 1
0 1 1
1 1 1
0 1 1
1 1 1
0 1 1
1 1 1
0 1 1
1 1 1
0 1 1
1 1 1
0 1 1
1 1 1
0 1 1
1 1 1
0 1 1
1 1 1
0 1 1
1 1 1
0 1 1
1 1 1
0 1 1
1 1 1
0 1 1
1 1 1
0 1 1
1 1 1
0 1 1
1 1 1
0 1 1
1 1 1
0 1 1
1 1 1
0 1 1
1 1 1
0 1 1
1 1 1
样例输出 2
1
2
4
16
65536
11418102
18325590
13700558
13700558
13700558
13700558
13700558
13700558
13700558
13700558
13700558
13700558
13700558
13700558
13700558

数据范围与提示

对于 0% 0 % 的测试点,和样例一模一样;
对于另外 10% 10 % 的测试点,没有修改;
对于另外 20% 20 % 的测试点,每次修改操作只会修改一个位置(也就是 l=r l = r ),并且每个位置至多被修改一次;
对于另外 10% 10 % 的测试点, p=2 p = 2
对于另外 10% 10 % 的测试点, p=3 p = 3
对于另外 10% 10 % 的测试点, p=4 p = 4
对于另外 20% 20 % 的测试点, n100,m100 n ≤ 100 , m ≤ 100
对于 100% 100 % 的测试点, 1n50000, 1 ≤ n ≤ 50000 , 1m50000, 1 ≤ m ≤ 50000 ,
1p100000000,0<c<p,0ai<p 1 ≤ p ≤ 100000000 , 0 < c < p , 0 ≤ a i < p

题解

这道题是一个神题,正解很神,我的方法比较玄学。
首先,看到 cx c x 应该想到用欧拉降幂公式:

ABmodp=ABmodφ(p)+φ(p)modp A B mod p = A B mod φ ( p ) + φ ( p ) mod p

当且仅当 Bp B ≥ p 时一定成立。

我们发现在不断进行 xcx x ← c x 的操作时,指数用欧拉降幂公式处理后最终会保持不变 。

 ccxmodp=ccxmodφ(p)+φ(p)modp=ccxmodφ(φ(p))+φ(φ(p))modφ(p)+φ(p)modp(843)(844)(845) (843)   c c x mod p (844) = c c x mod φ ( p ) + φ ( p ) mod p (845) = c c x mod φ ( φ ( p ) ) + φ ( φ ( p ) ) mod φ ( p ) + φ ( p ) mod p

如此不断反复,直到 φ(φ(p))=1 φ ( … φ ( p ) ) = 1 时,高层指数绘膜成 0 0 ,结果绘保持不变。

所以我们可以预处理出每一个数经过任意次赋值后得到的结果,直到结果保持不变后结束。我的方法是记忆化搜索, cal(x,cnt,p) c a l ( x , c n t , p ) 表示 x x 在进行 cnt c n t 次赋值后得到的结果 modp mod p 的值。判断指数是否大于膜数我使用的一种类似哈希的方法,计算两次快速幂,一遍膜对应的膜数,另一遍膜一个比膜数大的任意大质数(自然溢出绘被卡),比较两个结果是否相同。

我们把每一步得到的结果存下来,判断结果是否不变可以判断最后 x x x x 是一个常数,需要自行调参)位是否相同来实现。

然后维护数列可以使用线段树,修改时暴力修改区间里的每一个数,如果发现结果保持不变,就给该点打一个标记,发现当前区间所有节点都有标记就跳过这个区间,由降幂公式可以证明一个数修改的次数有限,可以保证复杂度。

总复杂度 O(nlog2n×) O ( n log 2 ⁡ n × 玄 学 )

My Code

#include <bits/stdc++.h>
#define lc k << 1
#define rc k << 1 | 1
using namespace std;
typedef long long ll;
ll p, c;

int n, m;
ll pri[1000005]; int cntpri;
ll phi[10000005]; int vis[10000005];
ll t1[100005];
void shai(){
    vis[1] = 1; phi[1] = 1;
    for(int i = 2; i <= 10000000; i ++){
        if(!vis[i]) pri[++cntpri] = i, phi[i] = i - 1;
        for(int j = 1; j <= cntpri && i * pri[j] <= 10000000; j ++){
            vis[i * pri[j]] = 1;
            if(i % pri[j] == 0){
                phi[i * pri[j]] = phi[i] * pri[j]; break;
            }
            phi[i * pri[j]] = phi[i] * (pri[j] - 1);
        } 
    }
}

ll getphi(ll x){
    if(x <= 10000000) return phi[x];
    ll tmp = x, ret = 1;
    for(int i = 1; pri[i] * pri[i] <= tmp; i ++){
        if(tmp % pri[i] == 0){
            tmp /= pri[i]; ret = ret * (pri[i] - 1);
            while(tmp % pri[i] == 0){
                tmp /= pri[i]; ret = ret * pri[i];
            }
        }
    }
    if(tmp > 1){
        ret = ret * (tmp - 1);
    }
    return ret;
}

ll qpow(ll a, ll b, ll mod){
    ll ret = 1;
    for(; b; b >>= 1, a = a * a % mod){
        if(b & 1) ret = ret * a % mod;
    }
    return ret;
}

ll qpow2(ll a, ll b){
    ll ret = 1;
    for(; b; b >>= 1, a = a * a % 998244353){
        if(b & 1) ret = ret * a % 998244353;
    }
    return ret;
}

ll phip[10005];
int tot;
vector<ll> vec[50005];
int vi2[7][30];
pair<ll, int> fafa[7][30];
pair<ll, int> cal(ll x, int d0, int d1){
    //printf("%lld %d %d\n", x, d0, d1);
    if(d0 <= 7){
        if(vi2[d0 - 1][d1]) return fafa[d0 - 1][d1];
        vi2[d0 - 1][d1] = 1;
    }

    if(d0 == 1) {
        ll tmp1 = qpow(c, x, phip[d1]);
        ll tmp2 = qpow2(c, x);
        if(tmp1 != tmp2) return fafa[d0 - 1][d1] = make_pair(tmp1, 1);
        else return fafa[d0 - 1][d1] = make_pair(tmp1, 0);
    }
    pair<ll, int> tmp3 = cal(x, d0 - 1, d1);

    if(tmp3.second == 1){
        pair<ll, int> tmp4 = cal(x, d0 - 1, d1 + 1);
        ll tmp1 = qpow(c, tmp4.first + phip[d1 + 1], phip[d1]);
        ll tmp2 = qpow2(c, tmp4.first + phip[d1 + 1]);
        if(d0 <= 7){
        if(tmp1 != tmp2) return fafa[d0 - 1][d1] = make_pair(tmp1, 1);
        else return fafa[d0 - 1][d1] = make_pair(tmp1, 0);
        }else{  
        if(tmp1 != tmp2) return make_pair(tmp1, 1);
        else return make_pair(tmp1, 0);
        }
    }else{
        ll tmp1 = qpow(c, tmp3.first, phip[d1]);
        ll tmp2 = qpow2(c, tmp3.first);
        if(d0 <= 7){
        if(tmp1 != tmp2) return fafa[d0 - 1][d1] = make_pair(tmp1, 1);
        else return fafa[d0 - 1][d1] = make_pair(tmp1, 0);
        }else{  
        if(tmp1 != tmp2) return make_pair(tmp1, 1);
        else return make_pair(tmp1, 0);
        }
    }

}

void pre(int i){
    memset(vi2, 0, sizeof(vi2));
    vec[i].push_back(t1[i]);
    vec[i].push_back(qpow(c, t1[i], p));
    while(1){
        ll tmp = cal(t1[i], vec[i].size(), 1).first;
        vec[i].push_back(tmp);
        //printf("%lld\n", tmp);
        int mn = max(int(vec[i].size() - 3), 0);
        if(mn == 0) continue;
        int flag = 0;
        for(int j = mn + 1; j < vec[i].size(); j ++){
            if(vec[i][j] != vec[i][j - 1]){
                flag = 1; break;
            }
        }
        if(flag == 0) break;
    }
}

struct node{
    ll dat;
    int dat2, tag;
    int l, r;
};

struct seg{
    node d[200005];

    void pushup(int k){
        d[k].dat = d[lc].dat + d[rc].dat;
        if(d[k].dat >= p) d[k].dat -= p;
        if(d[lc].tag && d[rc].tag) d[k].tag = 1;
    }

    void build(int k, int l, int r){
        d[k].l = l; d[k].r = r;
        d[k].dat = 0; d[k].dat2 = 0;
        d[k].tag = 0;
        if(l == r){
            d[k].dat = t1[l];
            if(d[k].dat2 == vec[d[k].l].size() - 1){
                d[k].tag = 1;
            }
            return;
        }
        int mid = (l + r) >> 1;
        build(lc, l, mid);
        build(rc, mid + 1, r);
        pushup(k);
    }

    void modify(int k, int l, int r){
        if(d[k].tag == 1) return;
        if(d[k].l == d[k].r){
            d[k].dat2++;
            d[k].dat = vec[d[k].l][d[k].dat2];
            if(d[k].dat2 == vec[d[k].l].size() - 1){
                d[k].tag = 1;
            }
            return;
        }
        int mid = (d[k].l + d[k].r) >> 1;
        if(l <= mid) modify(lc, l, r);
        if(r > mid) modify(rc, l, r);
        pushup(k);
    }

    ll query(int k, int l, int r){
        if(l <= d[k].l && d[k].r <= r){
            return d[k].dat;
        }
        ll sum = 0;
        int mid = (d[k].l + d[k].r) >> 1;
        if(l <= mid) sum += query(lc, l, r);
        if(r > mid) sum += query(rc, l, r);
        return sum % p;
    }
}Seg;
int main(){

    scanf("%d%d%lld%lld", &n, &m, &p, &c);
    shai();
    for(int i = 1; i <= n; i ++){
        scanf("%lld", &t1[i]);
    }
    phip[++tot] = p;
    for(int i = 1; i <= 1000; i ++){
        tot++;
        phip[tot] = getphi(phip[tot - 1]);
    }
    for(int i = 1; i <= n; i ++){
        pre(i);
    }
    Seg.build(1, 1, n);
    for(int i = 1; i <= m; i ++){
        int opt, l, r;
        scanf("%d%d%d", &opt, &l, &r);
        if(opt == 0){
            Seg.modify(1, l, r);
        }else{
            printf("%lld\n", Seg.query(1, l, r));
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值