4815: [Cqoi2017]小Q的表格

探讨了针对n*n数表的高效更新与求和算法,通过数学推导简化复杂度,利用分块技巧实现O(mlogn)的时间效率。算法核心在于找到gcd(i,j)相同的数对,通过预处理和分块求和策略,快速响应数表的修改及求和需求。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:

对于n*n的数表,任意一个位置,需要满足:
f(a,b)=f(b,a)f(a,b)=f(b,a)f(a,b)=f(b,a)

b×f(a,a+b)=(a+b)∗f(a,b)b×f(a,a+b)=(a+b)*f(a,b)b×f(a,a+b)=(a+b)f(a,b)
每次修改一个位置,需要将相关位置全部修改且求前k行前k列的和。

题解:

将二式变化可得:
f(a,a+b)a(a+b)=f(a,b)ab\frac{f(a,a+b)}{a(a+b)}=\frac{f(a,b)}{ab}a(a+b)f(a,a+b)=abf(a,b)
所以
f(a,b)ab=f(a,a−b)a(a−b)=f(a,a modb)a(a modb)=f(d,d)d2\frac{f(a,b)}{ab}=\frac{f(a,a-b)}{a(a-b)}=\frac{f(a,a\ modb)}{a(a\ modb)}=\frac{f(d,d)}{d^2}abf(a,b)=a(ab)f(a,ab)=a(a modb)f(a,a modb)=d2f(d,d)
其中d=gcd(a,b)d=gcd(a,b)d=gcd(a,b)
所以所有gcd(i,j)=gcd(a,b)gcd(i,j)=gcd(a,b)gcd(i,j)=gcd(a,b)的位置都会受影响。
枚举ddd
ans=∑dnf(d)∑i⌊nd⌋∑j⌊nd⌋[gcd(i,j)==1]ijans=\sum_d^nf(d)\sum_i^{\lfloor \frac n d \rfloor}\sum_j^{\lfloor \frac n d \rfloor}[gcd(i,j)==1]ijans=dnf(d)idnjdn[gcd(i,j)==1]ij
s(n)=∑in∑jn[gcd(i,j)==1]ijs(n)=\sum_i^n\sum_j^{n}[gcd(i,j)==1]ijs(n)=injn[gcd(i,j)==1]ij
根据∑in[(i,n)==1]i=ϕ(i)2∗n\sum_i^n[(i,n)==1]i=\frac{\phi(i)}{2}*nin[(i,n)==1]i=2ϕ(i)n
可得:
s(n)=∑ini2ϕ(i)s(n)=\sum_i^n i^2\phi(i)s(n)=ini2ϕ(i)
ans=∑dnf(d)s(⌊nd⌋)ans=\sum_d^nf(d)s(\lfloor \frac n d \rfloor)ans=dnf(d)s(dn)
分块即可。
但还有一个问题,fff是待修改的,假如树状数组求和过不去。
所以用普通分块。O(mlogn)O(mlogn)O(mlogn)
code:

#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#define LL long long
using namespace std;
const LL mod=1e9+7;
int m,n,L[2010],R[2010],cur[4000010];
int phi[4000010],pr=0,prime[4000010],h[4000010],f[4000010],sum[4000010];
int s[4000010],S[2010];
bool v[4000010];
void pre()
{
	memset(v,true,sizeof(v));
	phi[1]=1;
	for(int i=2;i<=4000000;i++)
	{
		if(v[i]) prime[++pr]=i,phi[i]=i-1;
		for(int j=1;j<=pr&&prime[j]*i<=4000000;j++)
		{
			int s=prime[j]*i;v[s]=false;
			if(i%prime[j]==0) {phi[s]=phi[i]*prime[j];break;}
			phi[s]=phi[i]*(prime[j]-1);
		}
	}
	for(int i=1;i<=4000000;i++) h[i]=(h[i-1]+(LL)i*i%mod*phi[i]%mod)%mod;
	for(int i=1;i<=4000000;i++) f[i]=(LL)i*i%mod,sum[i]=(sum[i-1]+f[i])%mod;
}
void change(int k,int c)
{
	for(int i=k;i<=R[cur[k]];i++) (s[i]+=c)%=mod;
	for(int i=cur[k];i<=cur[n];i++) (S[i]+=c)%=mod;
}
int gcd(int a,int b) {return a==0?b:gcd(b%a,a);}
int get(int k) {return (s[k]+S[cur[k]-1])%mod;}
int main()
{
	pre();
	scanf("%d %d",&m,&n);
	int len=sqrt(n);
	for(int i=1;i<=n;i++)
	{
		cur[i]=(i/len)+1;
		if(cur[i]!=cur[i-1]) L[cur[i]]=i,R[cur[i-1]]=i-1;
	}
	R[cur[n]]=n;
	for(int i=1;i<=n;i++)
	{
		s[i]=s[i-1];
		if(L[cur[i]]==i) s[i]=0,S[cur[i]]=S[cur[i]-1];
		s[i]=(s[i]+f[i])%mod;(S[cur[i]]+=f[i])%=mod;
	}
	while(m--)
	{
		int a,b,k;LL x;scanf("%d %d %lld %d",&a,&b,&x,&k);
		int d=gcd(a,b);a/=d;b/=d;
		x=x/a/b;x%=mod;change(d,-f[d]);f[d]=(int)x;change(d,f[d]);
		int i=1,j=0,ans=0;
		while(i<=k)
		{
			j=k/(k/i);
			ans=(ans+(LL)h[k/i]*(get(j)-get(i-1))%mod)%mod;
			i=j+1;
		}
		printf("%d\n",(ans+mod)%mod);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值