题解 P3700 【[CQOI2017]小Q的表格】
zhoutb2333 · · 题解
首先题目里说
然后
那么就有
发现这个形式很像辗转相除法求
即
发现如果修改值的话,
然后回去看
所以说我们可以根据
代码略丑,维护块的方式比较清奇
%:pragma GCC optimize(2)
#include<bits/stdc++.h>
#define maxn 4000010
#define ll long long
using namespace std;
int pri[maxn/8],phi[maxn],pricnt=0;
int bel[maxn],bpos[2010],epos[2010],size,tn,n,q;
//bel为每个位置属于哪个块,bpos,epos为每个块的初始位置和末尾位置
ll val[maxn],ans[maxn],ANS[2010],f[maxn];
//ans为块内前缀和,ANS为总前缀和
const ll p=1e9+7;
bool notpri[maxn];
inline int gcd(int x,int y){
if(!x||!y)
return x|y;
return x>y?gcd(y,x%y):gcd(x,y%x);
}
inline ll sum(int x){
if(!x)
return 0;
return (ANS[bel[x]-1]+ans[x])%p;
}
inline ll pw(ll a,ll b){
ll ret=1;
while(b){
if(b&1)
ret=ret*a%p;
a=a*a%p;
b>>=1;
}
return ret;
}
void init(){
phi[1]=1;
for(int i=2;i<=n;i++){
if(!notpri[i]){
pri[++pricnt]=i;
phi[i]=i-1;
}
for(int j=1;j<=pricnt;j++){
if(i*pri[j]>n)
break;
notpri[i*pri[j]]=true;
if(i%pri[j]==0){
phi[i*pri[j]]=phi[i]*pri[j];
break;
}
phi[i*pri[j]]=phi[i]*(pri[j]-1);
}
}
for(int i=1;i<=n;i++)
f[i]=(f[i-1]+1LL*phi[i]*i%p*i%p)%p;
}
int main(){
scanf("%d%d",&q,&n),size=sqrt(n),init();
for(int i=1;i<=n;i++){
bel[i]=i/size+1;
if(bel[i]>bel[i-1])
bpos[bel[i]]=i,epos[bel[i]-1]=i-1;
}
tn=bel[n],epos[tn]=n;
for(int i=1;i<=n;i++)
val[i]=1LL*i*i%p;
for(int i=1;i<=tn;i++){
for(int j=bpos[i];j<=epos[i];j++)
ans[j]=(ans[j-1]*(j>bpos[i])+val[j])%p;
ANS[i]=(ANS[i-1]+ans[epos[i]])%p;
}
for(int i=1;i<=q;i++){
int a,b,k,d,pos;
ll x,Ans=0;
scanf("%d%d%lld%d",&a,&b,&x,&k),x%=p,d=gcd(a,b);
val[d]=x*d%p*d%p*pw(1LL*a*b%p,p-2)%p;
for(int j=d;j<=epos[bel[d]];j++)
ans[j]=(ans[j-1]*(j>bpos[bel[d]])+val[j])%p;
for(int j=bel[d];j<=tn;j++)
ANS[j]=(ANS[j-1]+ans[epos[j]])%p;
for(int j=1;j<=k;j=pos+1){
pos=k/(k/j);
Ans=(Ans+f[k/j]*(sum(pos)-sum(j-1)+p)%p)%p;
}
printf("%lld\n",Ans);
}
return 0;
}