BZOJ 3813 奇数国

线段树+数论

看完这么一长串题目,现将有用的信息提取出来

首先$number*x+product*y=1$这个条件

如果要使$x$,$y$有解,那么$number$和$product$必须满足$\gcd (number,product)=1$

那么将询问操作转化为求出$[1,product]$中有多少个数与$product$互质

那么这个就是求$\varphi (product)$

因为欧拉函数的通项公式$\varphi (x)=x\prod_{i=1}^{k}(1-\frac{1}{p_{i}})$,p为x的质因数

观察题目,题目保证所有的钱都是由前60个素数相乘得到的

那么只要求出$product$就能通过逆元求出答案,19961993是质数,那么费马小定理即可

因为求$product$是区间的操作,所以用线段树维护每个银行的所存钱数

在线段树中要记录区间的乘积和当前区间出现的质数

因为只有60个质数,可以状压到一个long long中保存

那么pushup时也方便,直接将左儿子和右儿子的状态或运算即可

那么接下来的问题就是线段树单点修改,区间询问求出$product$了

最后要注意开long long时,常数也要转成long long(查这个查了一个小时)

#include <bits/stdc++.h>
#define mod (long long)19961993
using namespace std;
const long long MAXN=100100;
long long n,p[61],w,in[61];
struct node
{
    long long l,r;
    long long mask,mul;
}sh[MAXN*4];
bool check(long long x)//判断质数
{
    for (long long i=2;i*i<=x;i++)
    {
        if (x%i==0)
          return false;
    }
    return true;
}
long long split(long long x)//质因数分解
{
    long long m=0;
    for (long long i=0;i<w;i++)
    {
        if (x%p[i]==0)
          m|=(1ll<<i);//注意
    }
    return m;
}
long long m_pow(long long a,long long b)
{
    long long ans=1;
    while (b>0)
    {
        if (b&1)
          ans=(ans*a)%mod;
        a=(a*a)%mod;
        b>>=1;
    }
    return ans%mod;
}
long long inv(long long x)//用费马小定理求出逆元
{
    return m_pow(x,mod-2)%mod;
}
void pushup(long long x)
{
    sh[x].mul=(sh[x+x].mul*sh[x+x+1].mul)%mod;
    sh[x].mask=sh[x+x].mask|sh[x+x+1].mask;
}
void build(long long x,long long ll,long long rr)
{
    sh[x].l=ll;
    sh[x].r=rr;
    if (ll==rr)
    {
        sh[x].mul=(long long)3;//注意
        sh[x].mask=split((long long)3);
        return;
    }
    long long mid;
    mid=(ll+rr)>>1;
    build(x+x,ll,mid);
    build(x+x+1,mid+1,rr);
    pushup(x);
}
void change(long long x,long long wh,long long v)//线段树单点修改
{
    if (sh[x].l==wh && sh[x].r==wh)
    {
        sh[x].mul=v;
        sh[x].mask=split(v);
        return;
    }
    long long mid;
    mid=(sh[x].l+sh[x].r)>>1;
    if (wh<=mid)
      change(x+x,wh,v);
    else
      change(x+x+1,wh,v);
    pushup(x);
}
long long query1(long long x,long long ll,long long rr)//查找乘积
{
    if (sh[x].l>=ll && sh[x].r<=rr)
      return sh[x].mul;
    long long mid;
    long long ans=1;
    mid=(sh[x].l+sh[x].r)>>1;
    if (ll<=mid)
      ans=(ans*query1(x+x,ll,rr))%mod;
    if (rr>mid)
      ans=(ans*query1(x+x+1,ll,rr))%mod;
    pushup(x);
    return ans;
}
long long query2(long long x,long long ll,long long rr)//查找区间质数出现情况
{
    if (sh[x].l>=ll && sh[x].r<=rr)
      return sh[x].mask;
    long long mid;
    long long ma=0;
    mid=(sh[x].l+sh[x].r)>>1;
    if (ll<=mid)
      ma=ma|query2(x+x,ll,rr);
    if (rr>mid)
      ma=ma|query2(x+x+1,ll,rr);
    pushup(x);
    return ma;
}
int main()
{
    scanf("%lld",&n);
    for (long long i=2;i<=281;i++)
    {
        if (check(i))
        {
            p[w]=i;//预处理出前60个素数
            w++;
        }
    }
    for (int i=0;i<w;i++)
      in[i]=inv(p[i]);//及其逆元
    build(1,1,100000);
    while (n--)
    {
        long long a,b,c;
        scanf("%lld%lld%lld",&a,&b,&c);
        if (a==0)
        {
            long long pro,m;
            long long tot=1;
            pro=query1(1,b,c);
            m=query2(1,b,c);
            for (long long i=0;i<w;i++)
            {
                if ((m>>i)&1)
                  tot=(tot*(p[i]-1)%mod)*in[i]%mod;//欧拉函数通项公式
            }
            printf("%lld\n",(pro*tot)%mod);
        }
        else
        {
            change(1,b,c);
        }
    }
}

 

posted @ 2019-07-31 21:09  SevenDawns  阅读(171)  评论(0编辑  收藏  举报
浏览器标题切换
浏览器标题切换end