【洛谷P3700】小Q的表格

题目

题目链接:https://www.luogu.com.cn/problem/P3700
小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 ≤ 10^4, 1 ≤ a,b,k ≤ n ≤ 4×10^6, 0 ≤ x < 10^{18})

思路

太神啦 orz。
不难发现,如果我们更改 (f(x,y)),那么显然会连带着更改 (f(x,y+kx),f(x+ky,y))
发现这个东西很想更相减损法,其实不难发现,我们更改 (f(x,y)) 后,所有 (gcd(i,j)=gcd(x,y))(f(i,j)) 都会被更改。
既然如此,令 (d=gcd(x,y)),考虑寻找 (f(d,d))(f(x,y)) 之间的关系。由条件二可知,(f(d,kd)=k imes f(d,d))。拓展可得

[f(x,y)=frac{xy}{d^2}f(d,d) ]

那么我们更新 (f(x,y)) 的时候只需要更新 (f(d,d)) 即可。因为其他所有要更改的格子的 (gcd) 都是 (d),都可以通过 (f(d,d)) 推算。
那么设 (g(i)=f(i,i)),对于一次修改操作,直接把 (g(gcd(x,y))) 设为 (frac{gcd(x,y)^2}{xy}c) 即可。其中 (c) 是修改的数值。
对于一次询问操作,答案是

[ans=sum^{k}_{i=1}sum^{k}_{j=1}frac{ij}{gcd(i,j)^2}f(gcd(i,j)) ]

套路性把 (gcd(i,j)) 提出来

[=sum^{k}_{d=1}f(d)sum^{lfloorfrac{k}{d} floor}_{i=1}sum^{lfloorfrac{k}{d} floor}_{j=1}[gcd(i,j)=d]ij ]

这里我们需要知道一个结论:

[sum^{n}_{i=1}[gcd(i,n)=1]i=left{egin{matrix}frac{nvarphi(n)}{2} (n>1)\1 (n=1)end{matrix} ight. ]

证明非常简单,如果 (gcd(i,n)=1),那么 (gcd(n-i,n)=1),也就是对于任意一个与 (n) 互质的数字 (i),都存在另一个与 (n) 互质的数 (j)(i+j=n)。那么一共有 (frac{varphi(n)}{2}) 对这样的数字。
回到本题,我们可以把 (i) 看做 (n)(j) 看做 (i),那么

[ans=sum^{k}_{d=1}f(d)sum^{lfloorfrac{k}{d} floor}_{i=1}i^2varphi(i) ]

因为我们可以把 (j) 的枚举范围变成 ([1,i]),这样其实恰好做了一半。最后再把式子乘二就得到了上式。恰好 (1) 的贡献不用特判了。
然后整除分块就可以了。
但是我们需要支持单点修改和求前缀和,采用树状数组的话复杂度就是 (O(msqrt{n}log n)),无法接受。
发现除去这一部分时,修改操作复杂度 (O(m)),询问操作复杂度 (O(msqrt{n}))。我们希望一个修改 (O(sqrt{n})),询问 (O(1)) 的数据结构。
那么直接分块维护前缀和即可。算是一个比较经典的 trick 就不介绍了。
时间复杂度 (O(n+msqrt{n}))

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N=4000010,M=10010,MOD=1000000007;
int n,m,T,cnt,prm[N],bel[N];
bool v[N];
ll ans,phi[N],f[N],g[N],sum[2][N];

void findprm(int n)
{
	phi[1]=1;
	for (int i=2;i<=n;i++)
	{
		if (!v[i]) prm[++cnt]=i,phi[i]=i-1;
		for (int j=1;j<=cnt;j++)
		{
			if (i>n/prm[j]) break;
			v[i*prm[j]]=1; phi[i*prm[j]]=phi[i]*(prm[j]-1);
			if (i%prm[j]==0)
			{
				phi[i*prm[j]]=phi[i]*prm[j];
				break;
			}
		}
	}
}

ll fpow(ll x,ll k)
{
	ll ans=1;
	for (;k;k>>=1,x=x*x%MOD)
		if (k&1) ans=ans*x%MOD;
	return ans;
}

int gcd(int x,int y)
{
	return y?gcd(y,x%y):x;
}

ll calc(int i)
{
	return (sum[0][bel[i]]+sum[1][i])%MOD;
}

int main()
{
	scanf("%d%d",&m,&n);
	findprm(n);
	T=sqrt(n)+1;
	for (int i=1;i<=n;i++)
	{
		bel[i]=(i-1)/T+1;
		phi[i]=(phi[i-1]+phi[i]*i%MOD*i)%MOD;
		f[i]=1LL*i*i%MOD;
		g[i]=(g[i-1]+f[i])%MOD;
		sum[1][i]=(g[i]-g[(bel[i]-1)*T])%MOD;
	}
	for (int i=1;i<=T;i++) sum[0][i]=g[(i-1)*T];
	for (int o=1;o<=m;o++)
	{
		int x,y,k; ll p;
		scanf("%d%d",&x,&y);
		scanf("%lld",&p);
		scanf("%d",&k);
		int d=gcd(x,y);
		p=p%MOD*d%MOD*d%MOD*fpow(x,MOD-2)%MOD*fpow(y,MOD-2)%MOD;
		for (int i=bel[d]+1;i<=T;i++)
			sum[0][i]=(sum[0][i]+p-f[d])%MOD;
		for (int i=d;i<=bel[d]*T;i++)
			sum[1][i]=(sum[1][i]+p-f[d])%MOD;
		f[d]=p; ans=0;
		for (int l=1,r;l<=k;l=r+1)
		{
			r=k/(k/l);
			ans=(ans+(calc(r)-calc(l-1))*phi[k/l])%MOD;
		}
		printf("%lld
",(ans%MOD+MOD)%MOD);
	}
	return 0;
}
原文地址:https://www.cnblogs.com/stoorz/p/14294903.html