[CQOI2017]小Q的表格

Posted by Dispwnl on February 22, 2019

题目

题目描述

小Q是个程序员。

作为一个年轻的程序员,小Q总是被老C欺负,老C经常把一些麻烦的任务交给小Q来处理。每当小Q不知道如何解决时,就只好向你求助。

为了完成任务,小Q需要列一个表格,表格有无穷多行,无穷多列,行和列都从1开始标号。为了完成任务,表格里面每个格子都填了一个整数,为了方便描述,小Q把第a行第b列的整数记为f(a,b)。为了完成任务,这个表格要满足一些条件:

(1)对任意的正整数a,b,都要满足f(a,b)=f(b,a);

(2)对任意的正整数a,b,都要满足b×f(a,a+b)=(a+b)×f(a,b)。

为了完成任务,一开始表格里面的数很有规律,第a行第b列的数恰好等于a×b,显然一开始是满足上述两个条件的。为了完成任务,小Q需要不断的修改表格里面的数,每当修改了一个格子的数之后,为了让表格继续满足上述两个条件,小Q还需要把这次修改能够波及到的全部格子里都改为恰当的数。由于某种神奇的力量驱使,已经确保了每一轮修改之后所有格子里的数仍然都是整数。为了完成任务,小Q还需要随时获取前k行前k列这个有限区域内所有数的和是多少,答案可能比较大,只需要算出答案mod1,000,000,007之后的结果。

输入输出格式

输入格式:

输入文件第1行包含两个整数m,n,表示共有m次操作,所有操作和查询涉及到的行编号和列编号都不超过n。

接下来m行,每行4个整数a,b,x,k,表示把第a行b列的数改成x,然后把它能够波及到的所有格子全部修改,保证修改之后所有格子的数仍然都是整数,修改完成后计算前k行前k列里所有数的和。

输出格式:

输出共m行,每次操作之后输出1行,表示答案mod1,000,000,007之后的结果。

输入输出样例

输入样例#1:

3 3
1 1 1 2
2 2 4 3
1 2 4 2

输出样例#1:

9
36
14

输入样例#2:

4 125
1 2 4 8
1 3 9 27
1 4 16 64
1 5 25 125

输出样例#2:

2073
316642
12157159
213336861

说明

【输入输出样例 1 说明】

一开始,表格的前3行前3列如图中左边所示。前2次操作后表格没有变化,第3次操作之后的表格如右边所示。

1 2 3     2 4  6
2 4 6     4 4  12
3 6 9     6 12 9

对于 100%的测试点,1 ≤ m ≤ 10^4, 1 ≤ a,b,k ≤ n ≤ 4×10^6, 0 ≤ x < 10^18。

题解

先看第二个式子:$b\times f(a,a+b)=(a+b)\times f(a,b)$

可以化成:$\frac{f(a,a+b)}{a+b}=\frac{f(a,b)}{b}​$

即$\frac{f(a,a+b)}{a(a+b)}=\frac{f(a,b)}{ab}​$

设$c=a+b$,有$\frac{f(a,c)}{ac}=\frac{f(a,c-a)}{a(c-a)}=\frac{f(a,c-2a)}{a(c-2a)}=\frac{f(a,c\%a)}{a(c\%a)}=\frac{f(c\%a,a)}{(c\%a)a}$

最后有点像求$\gcd​$,所以可以直接化成$\frac{f(\gcd(a,c),\gcd(a,c))}{\gcd(a,c)^2}​$

所以$\frac{f(a,b)}{ab}=\frac{f(\gcd(a,b),\gcd(a,b))}{\gcd(a,b)^2}$

发现位置$(a,b)​$的值可以通过位置$(\gcd(a,b),\gcd(a,b))​$求出

则$f(\gcd(a,b),\gcd(a,b))=\frac{f(a,b)\times \gcd(a,b)^2}{ab}$,就可以维护$f(x,x)$的值了,这里的维护先按下不表

题目要求的是$\sum_{i=1}^{k}\sum_{j=1}^{k}f(i,j)$

即$\sum_{i=1}^{k}\sum_{j=1}^{k}\frac{f(\gcd(i,j),\gcd(i,j))\times ij}{\gcd(i,j)^2}$

$=\sum_{d=1}^{k}f(d,d)\sum_{i=1}^{k}\sum_{j=1}^{k}[\gcd(i,j)==d]\frac{ij}{d^2}​$

$=\sum_{d=1}^{k}f(d,d)\sum_{i=1}^{\left\lfloor\frac{k}{d}\right\rfloor}\sum_{j=1}^{\left\lfloor\frac{k}{d}\right\rfloor}[\gcd(i,j)==1]ij​$

后面的部分是个莫比乌斯反演的形式!根据套路可以化成:

$\sum_{d=1}^{k}f_{d,d}\sum_{x=1}^{\left\lfloor\frac{k}{d}\right\rfloor}\mu(x)x^2\sum_{i=1}^{\left\lfloor\frac{k}{dx}\right\rfloor}\sum_{j=1}^{\left\lfloor\frac{k}{dx}\right\rfloor}ij​$

然后怎么办……

可以整除分块套整除分块,再用一个支持单点修改+区间查询的什么玩意维护一下$f(x,x)$,能得$50$分的好成绩(并不)

当然你可以枚举$T=dx$,然后式子就成了$\sum_{T=1}^{k}\sum_{x\vert T}f(x,x)\mu(\frac{T}{x})(\frac{T}{x})^2$(大概),反正这玩意我不知道怎么快速维护……

再来看看这个式子

$\sum_{d=1}^{k}f(d,d)\sum_{i=1}^{\left\lfloor\frac{k}{d}\right\rfloor}\sum_{j=1}^{\left\lfloor\frac{k}{d}\right\rfloor}[\gcd(i,j)==1]ij​$

发现$i,j​$范围是一样的,则可以化成$\sum_{d=1}^{k}f(d,d)\times 2\sum_{i=1}^{\left\lfloor\frac{k}{d}\right\rfloor}i\sum_{j=1}^{i}[\gcd(i,j)==1]j​$

后面部分是求小于$i$与$i$互质的数的和,有公式$\sum_{i=1}^{n}[\gcd(i,n)==1]i=\frac{\phi(n)}{2}n$

因为如果$x​$与$n​$互质的话,$n-x​$与$n​$也互质,这样可以两两配对,共$\frac{\phi(n)}{2}​$对

这样式子就成了$\sum_{d=1}^{k}f(d,d)\sum_{i=1}^{\left\lfloor\frac{k}{d}\right\rfloor}\phi(i)i^2$

后面的部分可以预处理出来!这样就只需要一个整除分块了

现在考虑一个支持单点修改和维护前缀和数据结构来维护$f(x,x)​$

  • 我会树状数组!$O(\log n)​$的查询,$O(\log n)​$的修改,这样一次查询的复杂度就是$O(\sqrt n\times \log n)​$,有点卡时间但是树状数组常数小(你懂我意思吧)
  • 我会分块!$O(1)​$查询,$O(\sqrt n)​$修改,这样一次查询复杂度就是$O(\sqrt n)​$的,时间很充裕

实测分块比树状数组快$1s$左右,这里提供的是分块代码

代码

# include<iostream>
# include<cstring>
# include<cstdio>
# include<cmath>
# include<algorithm>
using namespace std;
const int MAX=4e6+5,mod=1e9+7,M=2e3+5;
int m,n,tot,k;
int phi[MAX],pm[MAX],val[MAX],sum[MAX],s[MAX],pos[MAX],bl[M],br[M],add[M];
bool use[MAX];
int gcd(int a,int b) {return b?gcd(b,a%b):a;}
void ss()
{
	phi[1]=sum[1]=1,val[1]=1,k=sqrt(n),pos[1]=bl[1]=br[1]=1;
	for(int i=2,x;i<=n;++i)
	  {
	  	val[i]=val[i-1]+1ll*i*i%mod,pos[i]=x=(i-1)/k+1,br[x]=i;
	  	if(val[i]>=mod) val[i]-=mod;
	  	if(!bl[x]) bl[x]=i;
	  	if(!use[i]) pm[++tot]=i,phi[i]=i-1;
	  	for(int j=1;j<=tot&&1ll*pm[j]*i<=n;++j)
	  	  {
	  	  	use[pm[j]*i]=1;
	  	  	if(i%pm[j]==0) {phi[pm[j]*i]=phi[i]*pm[j];break;}
	  	  	phi[pm[j]*i]=phi[i]*(pm[j]-1);
		  }
		sum[i]=(sum[i-1]+1ll*phi[i]*i%mod*i%mod)%mod+mod;
		if(sum[i]>=mod) sum[i]-=mod;
	  }
}
int read()
{
	int x(0);
	char ch=getchar();
	for(;!isdigit(ch);ch=getchar());
	for(;isdigit(ch);x=(1ll*x*10%mod+ch-48)%mod,ch=getchar());
	return x;
}
int Pow(int a,int b)
{
	int ans=1;
	for(;b;b>>=1,a=1ll*a*a%mod)
	  if(b&1) ans=1ll*ans*a%mod;
	return ans;
}
void change(int l,int r,int d)
{
	int Pl=pos[l],Pr=pos[r];
	if(Pl==Pr)
	{
		if(l==bl[Pl]&&r==br[Pr])
		{
			add[Pl]+=d;
			if(add[Pl]>=mod) add[Pl]-=mod;
			return;
		}
		for(int i=l;i<=r;++i)
		  {
		  	val[i]+=d;
		  	if(val[i]>=mod) val[i]-=d;
		  }
		return;
	}
	int L=Pl+(l!=bl[Pl]),R=Pr-(r!=br[Pr]);
	for(int i=L;i<=R;++i)
	  {
	  	add[i]+=d;
	  	if(add[i]>=mod) add[i]-=mod;
	  }
	if(L!=Pl) for(int i=l;i<=br[Pl];++i)
	  {
	  	val[i]+=d;
	  	if(val[i]>=mod) val[i]-=mod;
	  }
	if(R!=Pr) for(int i=bl[Pr];i<=r;++i)
	  {
	  	val[i]+=d;
	  	if(val[i]>=mod) val[i]-=mod;
	  }
}
int Cal(int x,int a,int b,int d) {return 1ll*x*d%mod*d%mod*Pow(a,mod-2)%mod*Pow(b,mod-2)%mod;}
int Cal_(int x,int d) {return (((x-val[d]+mod)%mod-add[pos[d]]+mod)%mod+(val[d-1]+add[pos[d-1]])%mod)%mod;}
int main()
{
	m=read(),n=read(),ss();
	for(int i=1,a,b,x,k,d,ans;i<=m;++i)
	  {
	  	a=read(),b=read(),x=read(),k=read(),d=gcd(a,b),x=Cal(x,a,b,d),change(d,n,Cal_(x,d)),ans=0;
		for(int l=1,r,N;l<=k;l=r+1)
	  	  {
	  	  	N=k/l,r=k/N,ans+=1ll*((val[r]+add[pos[r]])%mod-(val[l-1]+add[pos[l-1]])%mod+mod)%mod*sum[N]%mod;
			if(ans>=mod) ans-=mod;
		  }
		printf("%d\n",ans);
	  }
	return 0;
}