jzoj5636 【NOI2018模拟4.7】power (扩展欧拉定理,phi的性质)

86 篇文章 0 订阅
31 篇文章 0 订阅

Description

给定正整数m<=1e9与n个数,n<=1e5,q个询问,q<=1e5,每次询问某个区间[l,r]的power值:
alal+1al+2...ar a l a l + 1 a l + 2... a r %m

???

扩展欧拉定理就适合这种求幂取模的破玩意
这里写图片描述
第一条其实可以与下面两条合并,然后下面把gcd!=1去掉。

abmodp= a b mod p =
1.若 b<phi(p) b < p h i ( p ) ,a^b
2.若 b>=phi(p) b >= p h i ( p ) abmodphi(p)+phi(p) a b mod p h i ( p ) + p h i ( p )
具体证明可以网上找找,这里就不写了不会

然后我们可以发现,每一次询问,他的模数都会不断取phi,那么取phi时,奇数会变偶数,偶数会折半,那么最终到1只会有2 log m次左右。也就是说对一个数不断取他的phi,次数只有2 * log m次。

当l=r或p=1时可以直接return.
然后来解决b的问题,我们发现要找当前的b,只需要找到顺序第一个1,这个后面的都不用统计。 然后前面至少是2,是指数的指数级增长的,非常快(常数时间)就能判断出是否超过p了。
这样总的复杂度就是n log^2 n,还要带一些小常数,时限也开了4.5s。

#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
typedef long long ll;
typedef double db;
const ll N=1e5+10;
ll n,m,w[N],nx[N],q;
ll nphi[110];
ll min(ll a,ll b) {return a>b?b:a;}
ll gcd(ll a,ll b) {return b==0?a:gcd(b,a%b);}
ll ksm(ll a,ll b) {
    if (b==0) return 1; if (b==1) return a;
    ll t=ksm(a,b>>1);
    if ((db)t*t*ksm(a,b&1)>1e9) return 1e9+1;
    return t*t*ksm(a,b&1);
}

ll dmksm(ll a,ll b,ll mo) {
    if (b==0) return 1%mo;
    if (b==1) return a%mo;
    ll t=dmksm(a,b>>1,mo);
    return t*t%mo*dmksm(a,b&1,mo)%mo;
}

ll phi(ll x) {
    ll ret=1;
    for (int i=2; i*i<=x; i++) if (x%i==0) {
        int cs=0; while (x%i==0) x/=i,cs++;
        ret=ret*ksm(i,cs-1) * (i-1);
    }
    if (x!=1) ret*=(x-1);
    return ret;
}

ll get(ll l,ll r) {
    r=max(l,min(r,nx[l]-1));
    ll t=w[r];
    for (int i=r-1; i>=l; i--) {
        t=ksm(w[i],t); if (t>1e9) return 1e9+1;
    }
    return t;
}

ll f(ll l,ll r,ll mc) {
    ll mo=nphi[mc];
    if (mo==1 || l==r) return w[l]%mo;
    ll p=get(l+1,r);
    if (p>nphi[mc]) return dmksm(w[l],f(l+1,r,mc+1)+nphi[mc+1],nphi[mc]);
    else return dmksm(w[l],p,nphi[mc]);
}

int main() {
    freopen("power.in","r",stdin);
    freopen("power.out","w",stdout);
    cin>>n>>m;
    for (int i=1; i<=n; i++) scanf("%lld",&w[i]);
    nx[n+1]=n+1;
    for (int i=n; i; i--) nx[i]=w[i]!=1?nx[i+1]:i;
    ll tmp=m; for (int i=1; i<=100; i++,tmp=phi(tmp)) nphi[i]=tmp;
    cin>>q;
    for (int i=1;i<=q;i++) {
        int l,r; scanf("%d %d",&l,&r);
        printf("%lld\n",f(l,r,1));
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值