把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

P3700 [CQOI2017] 小 Q 的表格 题目分析

这道题目真的是将必修一的抽象函数用得淋漓尽致啊。

首先观察 \(f(a,b)=f(b,a)\),嗯,并没有什么鸟用,所以我们先不管他。

然后,就盯着 \(b\times f(a,a+b)=(a+b)\times f(a,b)\) 发呆吧。

我们还是可以发现:

\[\frac{f(a,a+b)}{a+b}=\frac{f(a,b)}{b} \]

熟悉课内题目的同学不难想到造个同构函数:

\[g(a,b)=\frac{f(a,b)}{b} \]

然后发现:

\[g(a,b)=g(a,a+b) \]

诶,这怎么那么像求 \(\gcd\) 时的更相减损术?

实际上我们可以得到:

\[g(a,b)=g(a,a+b)=g(a+b,a)=g(b,a) \]

怎么样,很震惊吧,我也是这么认为的。

那么我们更改 \(f(a,b)\) 这个值,相当于更改 \(g(a,b)\) 的值,又通过更相减损术可以得到其更改 \(g(x,y)\) 的值当且仅当 \(\gcd(x,y)=\gcd(a,b)\)

通过这个我们发现一个题目的性质,那就是:我们可以将任意 \(f(a,b)\) 的更改以及求贡献都可以算到 \(f(\gcd(a,b),\gcd(a,b))\),在物理中这被光荣地称为等效替代法

然后通过手玩:

\[f(d,d)\times 2d=f(d,2d)\times d\\ f(d,2d)\times 3d=f(d,3d)\times 2d \]

不难得到:\(f(d,kd)=\frac{k}{k-1}f(d,(k-1)d)=\frac{k}{k-1}\times\frac{k-1}{k-2}f(d,(k-2)d)=\dots=kf(d,d)\)

那么同理可得,当 \(\gcd(k_1,k_2)=1\) 时,\(f(k_1d,k_2d)=k_1\times k_2\times f(d,d)\)

那么对于更改 \(f(a,b)\) 怎么更改到 \(f(d,d)\) 呢?

有了上面的公式这个也变得简单了起来,具体而言是:

\[f(a,b)=f(d,d)\times\frac{ab}{d^2} \]

考虑题目要求:

\[\sum_{i=1}^n\sum_{j=1}^n f(i,j) \]

那么我们可以用上面的式子进行转化:

\[\sum_{i=1}^n\sum_{j=1}^n \frac{ij}{\gcd(i,j)^2}f(\gcd(i,j),\gcd(i,j)) \]

枚举一下 \(\gcd(i,j)\) 得到:

\[\sum_{d=1}^n\sum_{i=1}^{\lfloor \frac{n}{d}\rfloor}\sum_{j=1}^{\lfloor\frac{n}{d}\rfloor}ij\times f(d,d)[\gcd(i,j)=1] \]

\(f(d,d)\) 提到前面来:

\[\sum_{d=1}^n f(d,d)\sum_{i=1}^{\lfloor \frac{n}{d}\rfloor}\sum_{j=1}^{\lfloor\frac{n}{d}\rfloor}ij[\gcd(i,j)=1] \]

观察得到,后面的那一个式子可以用以下公式解决(很经典):

\[\sum_{i=1}^n[\gcd(i,n)=1]i=\frac{n\varphi(n)+[n=1]}{2} \]

我们考虑跟这个式子有什么关系:

\[\begin{align} &&&\sum_{i=1}^n\sum_{j=1}^n ij[\gcd(i,j)=1]\\ &&=&\sum_{i=1}^ni^2[\gcd(i,i)=1]+2\sum_{1\leq i<j\leq n} ij[\gcd(i,j)=1]\\ &&=&1+2\sum_{j=2}^n\frac{j^2\times\varphi(j)}{2}\\ &&=&\sum_{j=1}^nj^2\varphi(j)\\ &&=&\sum_{i=1}^ni^2\varphi(i) \end{align} \]

\(S(n)=\sum_{i=1}^ni^2\varphi(i)\),于是我们的式子变成了这个:

\[\sum_{d=1}^n S(\lfloor\frac n d\rfloor)f(d,d) \]

注意到:\(S(n)\) 可以 \(\mathcal{O}(n)\) 预处理,而这个式子又是一个典型的整除分块,我们就可以在查询的时候 \(\mathcal{O}(m\sqrt n)\) 的时间内回答了。

但是,由于会更改,这个 \(f(d,d)\) 的值还是会是时间复杂度上升,比如说你用树状数组维护这个单点修改以及区间查询的话,时间复杂度就会变为 \(\mathcal{O}(m(\log(n)+\sqrt n\log n))\)

那,主播主播有没有比较朴实的方法呢?

没错,我们就是要朴实的方法——分块!

怎么降时间复杂度呢,由于我们考虑的是查询的时候要 \(\mathcal{O}(1)\) 解决(指查出这个区间的和),那么我们可以把原本的序列看作一个前缀和,直接在更改的时候用分块处理这个前缀和,然后查询就是 \(\mathcal{O}(1)\) 的。

代码

总时间复杂度为 \(\mathcal{O}(n+m\sqrt n)\) 的,代码如下:

#include <iostream>
#include <cstdio>
#include <stdlib.h>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <vector>
#define int long long
#define N 4000006
#define M 500005
#define P 3005
using namespace std;
const int mod = 1e9 + 7;
bool vis[N];
int prime[M],cnt,phi[N],f[N],sum[N],sum2[N];
void init(int n) {
	phi[1] = 1;
	for (int i = 2;i <= n;i ++) {
		if (!vis[i]) prime[++cnt] = i,phi[i] = i - 1;
		for (int j = 1;j <= cnt && prime[j] * i <= n;j ++) {
			vis[prime[j] * i] = 1;
			if (i % prime[j] == 0) {
				phi[i * prime[j]] = phi[i] * prime[j];
				break;
			}
			phi[i * prime[j]] = phi[i] * (prime[j] - 1);
		}
	}
	for (int i = 1;i <= n;i ++) f[i] = i * i % mod;
	for (int i = 1;i <= n;i ++) sum[i] = (sum[i - 1] + phi[i] * i % mod * i % mod) % mod;
	for (int i = 1;i <= n;i ++) sum2[i] = (sum2[i - 1] + f[i]) % mod;
}
int gcd(int a,int b) {
	return b ? gcd(b,a % b) : a;
}
int n,m,bl[N],L[P],R[P],lz[P];
void build() {
	int len = ceil(sqrt(1.0 * n));
	for (int i = 1;i < P;i ++) L[i] = 1145141919;
	for (int i = 1;i <= n;i ++) {
		int t = i / len + 1;
		bl[i] = t;
		R[t] = i;
		L[t] = min(L[t],i);
	}
	R[bl[n] + 1] = 1e9;
//	for (int i = 1;i <= bl[n];i ++) lz[i] = 0;
}
void update(int r,int val) {
//	val = (val % mod + mod) % mod;
//	cout << sum2[r] << ' ' << val << ' ' << (sum2[r] +) << '\n';
	int pos = bl[r];
	if (r == L[pos]) lz[pos] = (lz[pos] + val + mod) % mod;
	else for (int i = r;i <= R[pos];i ++) sum2[i] = (sum2[i] + val + mod) % mod;
	pos ++;
	for (;R[pos] <= n;pos ++) lz[pos] = (lz[pos] + val + mod) % mod;
//	cout << endl; 
}
int query(int pos) {
//	cout << pos << ' ' << sum2[pos] << endl;
	return ((sum2[pos] + lz[bl[pos]]) % mod + mod) % mod;
}
signed main(){
	init(4e6);
	cin >> m >> n;
	build();
	for (int a,b,x,k;m --;) {
		scanf("%lld%lld%lld%lld",&a,&b,&x,&k);
		int t = gcd(a,b);
		int ans = 0;
		update(t,-f[t]);
		f[t] = x / ((a / t) * (b / t));
//		cout << f[t] << endl;
//		cout <<2;
		update(t,f[t]);
		for (int l = 1,r;l <= k;l = r + 1) {
			r = k / (k / l);
//			cout << l << ' ' << r << ' ' << query(r) << ' ' << query(l - 1) << '\n';
			ans = (ans + sum[k / l] * (query(r) - query(l - 1) + mod) % mod) % mod;
		}
		printf("%lld\n",ans);
	}
	return 0;
}
/*
6 4000000
10 2345 10000 4000000
100 2345 10000 4000000
1000 2345 10000 4000000
77 22 42494 142992 494924
94 492 92924 249294 24994
294 294 29494 2949222 949492

806651387
209058084
253769940
691839689
637913350
765583133

*/
posted @ 2025-08-12 22:34  high_skyy  阅读(11)  评论(0)    收藏  举报
浏览器标题切换
浏览器标题切换end