「SHOI2017」相逢是问候

本文详细解析了SHOI2017竞赛题目“相逢是问候”的解题思路及实现代码。重点介绍了如何利用扩展欧拉定理解决幂次运算中的取模问题,通过分块和预处理技术优化算法复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

「SHOI2017」相逢是问候

 

这道题真的难受,本来一眼望过去以为随便写写就60分了,结果发现<100那块部分分拿不了拿不了,因为修改过程中是不能直接对p取模的。一般想到那块部分分怎么做正解也就出来了。

首先了解一下扩展欧拉定理,对于a^b%p,当gcd(a,p)=1的时候a^b%p=a^(b%phi[p])%p,当gcd(a,p)不为1的时候,若b<phi[p],则a^b%p=a^b%p,不然a^b%p=a^(b%phi[p]+phi[p])%p。

那么看一下这道题实质上是在求什么,修改一次变为c^a,两次变为c^(c^a),三次变为c^(c^(c^a))).......

如果求c^(c^a)%p可以通过扩展欧拉定理变换为c^(c^a%phi[p]+(c^a>=phi[p])*phi[p])%p,我们发现里面又变成了一个子问题,即求c^a%phi[p],此时已经可以直接求解了,如果修改更多次,可以重复这个过程,那么就是对模数不停的取phi,我们发现,取得次数达到一定次数的时候,这个值将不会再改变,因为当某一层phi为1的时候,如果再次修改,到达phi=1那一层,值必然会变成0,无论怎样递归下去,到达此层的值已经固定了。但是存在一种特殊情况,即a=0的时候,这个时候当phi=1了,我们还需要再进行一次才能保证值会不变,因为0<1,而再进行一次操作之后的值=1,那么对于(c^a>=phi[p])*phi[p]这一部分,他们会得到不同的情况,所以需要在phi=1之后额外进行一次,而且只需要进行一次,因为之后再也不会出现=0的情况了。可以证明,进行log级别次操作后phi会变成1,虽然我不会证明,可以当作结论来记来记吧。那么就可以类似于区间开平方一样,修改时暴力修改,并标记是否已经全部修改到上限了。分块和线段树均可,并且复杂度相同,因为此题瓶颈在于修改的nlog^3,但是这样似乎过不去,还需要对快速幂进行优化,因为从始至终,都是在对c进行快速幂,所以可以预处理出来c^(1...1<<16)和c^((1<<16)*(1...1<<16))的值,O1的求解。

当然此题有一个小问题,也是纠结了我好几天的问题,就是我们判断c^()幂是否大于模数的时候,是不能用其模意义下的值来比较的,但是事实上这样做并不会有影响,听说有人验证过,然而我也不清楚这个。

 

 

  1 #include<bits/stdc++.h>
  2 #define LL long long
  3 using namespace std;
  4 const int inf=5e4+10;
  5 int n,m,p,c;
  6 int limit,phi[inf];
  7 int a[inf];
  8 int get_phi(int x){
  9     int ans=x;
 10     for(int i=2;i*i<=x;i++){
 11         if(x%i)continue;
 12         ans=ans/i*(i-1);
 13         while(x%i==0)x/=i;
 14     }
 15     if(x>1)ans=ans/x*(x-1);
 16     return ans;
 17 }
 18 int pow1[100][1<<16],pow2[100][1<<16],Cnt[100];
 19 int fast(int x,int y){
 20     if(c==1)return 1;
 21     if(x<Cnt[y])return pow1[y][x];
 22     int u=x>>16;
 23     return 1ll*pow1[y][x-(u<<16)]*pow2[y][u]%phi[y]+phi[y];
 24 }
 25 int get(int x,int y){
 26     int ans=x;
 27     for(int i=y-1;i>=0;i--)
 28         ans=fast(ans,i);
 29     return ans%p;
 30 }
 31 int b[inf];
 32 int len,pos[inf],l[inf],r[inf];
 33 int sum[inf],cnt[inf],mn[inf];
 34 void modify(int x,int y){
 35     for(int i=x;i<=min(y,r[pos[x]]);i++){
 36         if(cnt[i]==limit)continue;
 37         cnt[i]++;
 38         sum[pos[i]]-=b[i];
 39         b[i]=get(a[i],cnt[i]);
 40         sum[pos[i]]+=b[i];
 41         sum[pos[i]]%=p;
 42         sum[pos[i]]=(sum[pos[i]]+p)%p;
 43     }
 44     mn[pos[x]]=0x3fffffff;
 45     for(int i=l[pos[x]];i<=r[pos[x]];i++)
 46         mn[pos[i]]=min(mn[pos[i]],cnt[i]);
 47     for(int i=pos[x]+1;i<pos[y];i++){
 48         if(mn[i]==limit)continue;
 49         mn[i]=0x3fffffff;
 50         for(int j=l[i];j<=r[i];j++){
 51             if(cnt[j]==limit)continue;
 52             cnt[j]++;
 53             mn[i]=min(mn[i],cnt[j]);
 54             sum[i]-=b[j];
 55             b[j]=get(a[j],cnt[j]);
 56             sum[i]+=b[j];
 57             sum[i]%=p;
 58             sum[i]=(sum[i]+p)%p;
 59         }
 60     }
 61     if(pos[x]!=pos[y]){
 62         for(int i=l[pos[y]];i<=y;i++){
 63             if(cnt[i]==limit)continue;
 64             cnt[i]++;
 65             sum[pos[i]]-=b[i];
 66             b[i]=get(a[i],cnt[i]);
 67             sum[pos[i]]+=b[i];
 68             sum[pos[i]]%=p;
 69             sum[pos[i]]=(sum[pos[i]]+p)%p;
 70         }
 71         mn[pos[y]]=0x3fffffff;
 72         for(int i=l[pos[y]];i<=r[pos[y]];i++)
 73             mn[pos[i]]=min(mn[pos[i]],cnt[i]);
 74     }
 75 }
 76 int query(int x,int y){
 77     int ans=0;
 78     for(int i=x;i<=min(r[pos[x]],y);i++)
 79         ans=(ans+b[i])%p;
 80     for(int i=pos[x]+1;i<pos[y];i++)
 81         ans=(ans+sum[i])%p;
 82     if(pos[x]!=pos[y])
 83         for(int i=l[pos[y]];i<=y;i++)
 84             ans=(ans+b[i])%p;
 85     return ans;
 86 }
 87 int main()
 88 {
 89     scanf("%d%d%d%d",&n,&m,&p,&c);
 90     for(int i=1;i<=n;i++)scanf("%d",&a[i]),b[i]=a[i];
 91     phi[0]=p;
 92     while(phi[limit]!=1)
 93         phi[++limit]=get_phi(phi[limit-1]);
 94     phi[++limit]=1;
 95     for(int i=0;i<=limit;i++){
 96         pow1[i][0]=1%phi[i];
 97         for(int j=1;j<(1<<16);j++)
 98             pow1[i][j]=1ll*pow1[i][j-1]*c%phi[i];
 99         int u=1ll*pow1[i][(1<<16)-1]*c%phi[i];
100         pow2[i][0]=1%phi[i];
101         for(int j=1;j<(1<<16);j++)
102             pow2[i][j]=1ll*pow2[i][j-1]*u%phi[i];
103         LL now=1;
104         while(c!=1 && now<phi[i])now=now*c,Cnt[i]++;
105     }
106     len=sqrt(n);
107     for(int i=1;i<=n;i++)
108         sum[pos[i]=(i-1)/len+1]+=a[i],
109         sum[pos[i]]%=p;
110     for(int i=1;i<=n;i++){
111         if(pos[i]!=pos[i-1])l[pos[i]]=i;
112         if(pos[i]!=pos[i+1])r[pos[i]]=i;
113     }
114     for(int i=1;i<=m;i++){
115         int opt,x,y;
116         scanf("%d%d%d",&opt,&x,&y);
117         if(opt)
118             printf("%d\n",query(x,y));
119         else
120             modify(x,y);
121     }
122     return 0;
123 }
View Code

 

转载于:https://www.cnblogs.com/hyghb/p/8553354.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值