题解 P3700 【[CQOI2017]小Q的表格】

zhoutb2333

2018-04-12 09:49:12

Solution

首先题目里说$f(a,b)=f(b,a)$,说明我们只用维护半个棋盘的信息就够了。 然后$b *f(a,a+b)=(a+b)*f(a,b)$这个式子我们化成$\frac{f(a,b)}{a*b}=\frac{f(a,a+b)}{a*(a+b)}$ 那么就有$\frac{f(a,b)}{a*b}=\frac{f(a,a \% b)}{a*(a \% b)}$ 发现这个形式很像辗转相除法求$\gcd$的过程,所以我们如果一直这样递归下去的话可以得到$\frac{f(a,b)}{a*b}=\frac{f(\gcd(a,b),\gcd(a,b))}{\gcd^2(a,b)}$。 即$f(a,b)=\frac{a*b}{d^2}*f(d,d)$,令$d=\gcd(a,b)$ 发现如果修改值的话,$\gcd$不同的位置的$f$值相互独立。也就是说修改一个位置$(a,b)$的值后有且仅有$\gcd(x,y)=d$的$(x,y)$位置的$f$值会跟着变化。(这句话对求答案没什么用,是为方便理解写的) 然后回去看$ans$, $$\sum \limits ^{k}_{i=1}\sum \limits ^{k}_{j=1} f(i,j)$$ $$= \sum \limits ^{k}_{d=1} f(d,d) \sum \limits _{d|i} \sum \limits _{d|j} \frac{i*j}{d^2} \varepsilon(\gcd(i,j)==d)$$ $$= \sum \limits ^{k}_{d=1} f(d,d) \sum \limits^{\lfloor \frac{k}{d} \rfloor} _{i=1} \sum \limits^{\lfloor \frac{k}{d} \rfloor} _{j=1} i*j* \varepsilon(\gcd(i,j))$$ 所以说我们可以根据$k/d$分块,这样需要动态维护$f(i,i)$的前缀和。观察到$n$为 $4*10^6$而$m$为$10^4$,所以我们大概需要一个数据结构,支持单点修改$O(\sqrt{n})$,查前缀和$O(1)$。分块正好可以做到这个。具体就是块内每个位置维护本块内的前缀和,然后每个块末尾维护一下总的前缀和,这样查询的时候等于上一块的总答案加上这个位置的块答案。修改暴力重构,只会影响到$\sqrt{n}$左右个值。 设$G(x)=\sum \limits^{x} _{i=1} \sum \limits^{x} _{j=1} i*j* \varepsilon(\gcd(i,j))$。那么根据$\sum \limits ^{x}_{i=1}i*\varepsilon(\gcd(i,x))=\frac{x*(\varphi(x)+\varepsilon(x))}{2}$,有$G(x)=\sum \limits ^{x}_{i=1} i^2 *\varphi(i)$(考虑$G(x)$比$G(x-1)$多了什么),这个东西可以线性筛预处理。(然而蒟蒻这一步只会$n \sqrt{n}$的算法导致做不出来了..QAQ) 注意不要爆$int$ 代码略丑,维护块的方式比较清奇 ``` cpp %: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; } ```