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); } } }