bzoj4869&&jzoj5214[Shoi2017]相逢是问候 线段树+欧拉定理

来源:互联网 发布:freemind软件 编辑:程序博客网 时间:2024/05/17 03:04

真是码农啊,当然可能是我比较菜= =码死我了。。
考场(训练)上觉得自己两题稳了所以只打30分,发现满分神TM难打。

题意:给你一个序列,m个操作,询问区间和%p,还有修改,每次修改v把v变成c^v.
给出n,m,p,c

没什么想法,看了题解发现是神题= =

首先我们需要几个结论:
扩展欧拉定理:
当x>=phi(p)时,c^x=c^(x%phi(p)+phi(p))mod p;
这个结论nb之处,在于p可以不是素数,甚至不用互质= =证明看po姐在bzoj3884写的题解
1.每个数不断的取phi,logp次以后再修改值不变。
这个很好证明,每次要不然奇数变偶数,要不然偶数取一半= =。
2.根据推论1,一个数被修改logp次以后再修改值不变。
证明略,比较复杂就不写了。
其他题解好像有些吧(懒癌患者晚期)

那么我们可以发现其实一个数假设被修改k次,那他其实相当于这样一个式子:
c^(c^(c^(c^(c^…(a[i]))))) ①
定义 p[i] 为 P 取过 i 次 phi 之后得到的数,即 p[0]
= P, p[i] = phi(p[i-1])

根据欧拉定理,我们可以发现,在计算上面那个式子的时候,由于c^x是迭代的,所以要%的数p也是迭代的,所以要把p[]求出来,这个自己推一下就好了。
然后具体做法如下:

先预处理出p,然后线段树修改的时候直接用快速幂暴力算,注意判断一下如果修改超过p数组的长度(再大p是0没有意义)就直接跳出,这样是均摊log的。
但是这样是log^3的,为什么呢。。
觉得是log^2的人都挺naive的

在这里我们默认了修改一次的时间是O(1),然而事实并非如此。对于一个数的快速幂,需要递归O(logP)层(因为每层指数的模数都是不一样的,都要重新算),所以一个数快速幂就要两个log,一共有nlogP个数(对不是n个数,x、c^x、c(c^x)……这些都要重新算的),所以快速幂的预处理就变成了三个log!虽然开了O2,但是如果常数爆炸的话很有可能被卡掉。 

所以我们要对快速幂进行离线处理,用分块算出前2^16和后面的。由于c最大1e9,我们我们把它看作1<<31,分成两半。
提前算出p以后枚举一下预处理就好了,p<=1e8,所以p数组的最大个数是26(logp),然后修改的时间复杂度就可以算作是常数级别的了。
代码巨丑(bzoj AC)

#include <cstdio>#include <algorithm>#define fo(i,a,b) for(int i=a;i<=b;i++)using namespace std;template <class T>T Min(const T &a, const T &b) {return a < b ? a : b;}template <class T>T Max(const T &a, const T &b) {return a > b ? a : b;}typedef long long ll;const int SN = 100000 + 10;int k, cnt, phi[SN];ll sum[SN<<2], time[SN<<2], c, p, a[SN];int table[32][1<<16][2];void Read(ll &x) {    ll in = 0, f = 1; char ch = getchar();    while(ch<'0' || ch>'9') {if(ch=='-') f = -1; ch = getchar();}    while(ch>='0' && ch<='9') {in = in*10+ch-'0'; ch = getchar();}    x = in*f;}int get_phi(int n){     int res = n, a = n;      for(int i = 2;i * i <= a;i++){      if(a%i==0){          res=res/i*(i-1);          while(a%i==0) a/=i;      }      }      if(a>1) res=res/a*(a-1);      return res;  }  int pow(int a,int b,int MOD){    int res = 1;    while(b) {    if(b & 1) res = (ll)res * a % MOD;    a = (ll)a * a % MOD;    b >>= 1;    }    return res;}void pre() {    phi[0] = p;    while(phi[k] != 1) {++k;phi[k] = get_phi(phi[k-1]);}    phi[++k] = 1;    fo(i,0,k)    {        int r=pow(c,1<<16,phi[i]);        table[i][0][0]=table[i][0][1]=1;        for(int j=1;j<(1<<16);j++)        {            table[i][j][0]=1ll*table[i][j-1][0]*c%phi[i];            table[i][j][1]=1ll*table[i][j-1][1]*r%phi[i];        }    }}inline ll Power(int a,int b){    return 1ll*table[b][a&65535][0]*table[b][a>>16][1]%phi[b];}ll Calc(int x, int t) {    int res=x;    if (res>=phi[t])res=res%phi[t]+phi[t];    while (t--)    {        x=res;        res=Power(x,t);        if(t&&!res)res+=phi[t];    }    return res%p;}void pushup(int rt) {    sum[rt] = (sum[rt<<1] + sum[rt<<1|1])%p;    time[rt] = Min(time[rt<<1], time[rt<<1|1]);}void build(int l, int r, int rt) {    if(l == r) {    Read(sum[rt]), a[l] = sum[rt];    a[l] %= p, sum[rt] %= p;    return ;    }    int mid = (l+r)>>1;    build(l, mid, rt<<1), build(mid+1, r, rt<<1|1);    pushup(rt);}void Modify(int QL, int QR, int l, int r, int rt) {    if(time[rt] >= k) return ;    if(l == r) {    time[rt]++, sum[rt] = Calc(a[l], time[rt]);    return ;    }    int mid = (l+r)>>1;    if(QL <= mid) Modify(QL, QR, l, mid, rt<<1);    if(QR > mid) Modify(QL, QR, mid+1, r, rt<<1|1);    pushup(rt);}ll Query(int QL, int QR, int l, int r, int rt) {    if(QL <= l && QR >= r) return sum[rt]%p;    int mid = (l+r)>>1; ll ans = 0;    if(QL <= mid) ans = (ans + Query(QL, QR, l, mid, rt<<1))%p;    if(QR >  mid) ans = (ans + Query(QL, QR, mid+1, r, rt<<1|1))%p;    return ans;}int main() {    ll n, m, opt, x, y;    Read(n), Read(m), Read(p), Read(c);    pre();    build(1, n, 1);    for(int i = 1; i <= m; i++)     {        Read(opt), Read(x), Read(y);        if (!opt) Modify(x, y, 1, n ,1);        else        printf("%lld\n",Query(x,y,1,n,1)%p);    }    return 0;}
阅读全文
0 0
原创粉丝点击