2024牛客多校第九场 C.Change Matrix 欧拉反演

这题是欧拉反演的应用,之前没学过欧拉函数和欧拉反演,傻傻对着 gcd(i,j) 不知道怎么化简。

首先对原来的矩阵进行转化,拆成 n 个小矩阵

因为gcd(i,j)=x|i,x|jϕ(x)

这是因为对于任意的正整数 n 都有 n=d|nϕ(d),证明见oiwiki:https://oi-wiki.org/math/number-theory/euler-totient/

那把 n 替换成 gcd(i,j) 就得到上面的式子。但这个式子只是对于 i,j 固定为某个值的情况,考虑枚举 i1nj 同理。得到:

i=1i=nj=1j=ngcd(i,j)=i=1i=nj=1j=nc|i,c|jϕ(c)

反演有一个常见套路就是把最后枚举的这个 c 提到前面来(这跟在莫比乌斯反演里经常把 d 提到前面来是一样的),所以调换求和顺序把式子改写成:

c=1c=nϕ(c)i=1i=nj=1j=n[c|i][c|j]

(其中 [c|i] 的意思是若 c 整除 i ,则该表达式的值为 1 ,否则为 0 )

观察到 i[1,n] 时,满足 [c|i]i 共有 n/c 个,j 同理,所以还可以写成:

c=1c=nϕ(c)n/cn/c

这个式子的含义是,对于每个 c ,它都对应了一个长和宽都为 n/c 的矩阵,且矩阵内每个元素的初始值都为 ϕ(c) 。第 c 个矩阵的行列 (i,j) 在原矩阵中对应的位置是 (ic,jc)
比如当前第 c 个矩阵长这样(只描述下标,每个位置对应的权值都是 ϕ(c) ):

[1,11,21,3...1,n/c2,12,22,3...2,n/c...n/c,1n/c,2n/c,3...n/c,n/c]

则在原矩阵中它们对应的位置为:

[c,cc,2cc,3c...c,n/cc2c,c2c,2c2c,3c...2c,n/cc...n/cc,cn/cc,2cn/cc,3c...n/cc,n/cc]

显然一共有 n 个矩阵 ( c[1,n] ),所有矩阵的元素和加起来就等于我们要求的值。转化的意义在于把原本的大矩阵拆成了若干个小矩阵,每个小矩阵的 ϕ() 都相等,原本矩阵里某个位置(i,j)的值就等于它出现过的小矩阵在该位置累加的和,有点像分层。

再放个雨巨的图方便大家理解:

c=1

c=2

c=3

c=4

接下来考虑每次操作对这些矩阵的影响

行和列是等价的,故只考虑行。

回到之前的这个式子:

i=1i=nj=1j=n[c|i][c|j] ,当改动第 x 行时,影响的是 i=x 和所有满足 c|xc (暂且先不考虑后面的 j )。所以对于 x 的每个因数 c , 第 c 个矩阵都会受影响。

具体是什么影响?观察图可知,在原来的大矩阵里改一行时,对应的小矩阵也是改一整行。只不过对于第 c 个矩阵来说,x 在这个矩阵里对应的是第 x/c 行,所以改的是第 x/c 行(这里有点难解释,多悟一悟)。

那我们可以用数组 r[c][i] 表示第 c 个矩阵第 i 行的系数,进行 R x y 操作时就是:
r[c][x/c]*=y

答案 ans=c=1c=nϕ(c)( r(i,j) )( c(i,j) ) ,每次修改时减去原来的贡献,进行修改,再加上新的贡献,具体细节见代码。

code:

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int mod = 1e9+7;
const int maxn = 100005;
int phi[maxn];
int prime[maxn], cnt;
bool vist[maxn + 1];
void Euler(int n){
	phi[1] = 1;
	for (int i = 2; i <= n; i ++){
		if (!vist[i]) prime[++ cnt] = i, phi[i] = i - 1;
		for (int j = 1; j <= cnt && i * prime[j] <= n; j ++){
			vist[i * prime[j]] = true;
			if (i % prime[j] != 0) phi[i * prime[j]] = phi[i] * (prime[j] - 1);
			else {
				phi[i * prime[j]] = phi[i] * prime[j];
				break;
			}
		}
	}
}
int n,q;
int sumR[maxn],sumC[maxn];
vector<int>r[maxn],c[maxn];// 第i个矩阵的第j行系数和第i个矩阵的第j列系数
void solve(){
	cin>>n>>q;
	for(int i=0;i<=n;i++) r[i].clear(),c[i].clear();
	int ans=0;
	for(int i=1;i<=n;i++){
		r[i].push_back(0);
		c[i].push_back(0);
		sumR[i]=sumC[i]=0;
		for(int j=1;j<=n/i;j++)
			r[i].push_back(1),c[i].push_back(1),// 最开始系数都为1
			sumR[i]++,sumC[i]++;//sumR[i]表示第i个矩阵的行系数和
	}
	for(int i=1;i<=n;i++){
		ans+=phi[i]*(n/i)%mod*(n/i)%mod;//初始的答案
		ans%=mod;
	}

	while(q--){
		char op;cin>>op;
		if(op=='R'){
			int x,y;cin>>x>>y;
			y%=mod;
			for(int i=1;i*i<=x;i++){
				// 处理第i个矩阵
				if(x%i) continue;
				// 减去原来的贡献
				ans-=((phi[i]*r[i][x/i]%mod)*sumC[i])%mod;
				ans=(ans+mod)%mod;
				sumR[i]-=r[i][x/i];
				sumR[i]=(sumR[i]+mod)%mod;
				// 修改r[][]和sumR[]
				r[i][x/i]=r[i][x/i]*y%mod;
				sumR[i]+=r[i][x/i];
				sumR[i]%=mod;
				ans+=((phi[i]*r[i][x/i]%mod)*sumC[i])%mod;
				ans%=mod;
				
				// 处理x/i
				if(i*i!=x){
					int j=x/i;
					ans-=((phi[j]*r[j][x/j]%mod)*sumC[j])%mod;
					ans=(ans+mod)%mod;
					sumR[j]-=r[j][x/j];
					sumR[j]=(sumR[j]+mod)%mod;
					// update
					r[j][x/j]=r[j][x/j]*y%mod;
					sumR[j]+=r[j][x/j];
					sumR[j]%=mod;
					ans+=((phi[j]*r[j][x/j]%mod)*sumC[j])%mod;
					ans%=mod;
				}
			}
		}
		else {
			// 列同理
			int x,y;cin>>x>>y;
			y%=mod;
			for(int i=1;i*i<=x;i++){
				// 处理第i个矩阵
				if(x%i) continue;
				// change
				ans-=((phi[i]*c[i][x/i]%mod)*sumR[i])%mod;
				ans=(ans+mod)%mod;
				sumC[i]-=c[i][x/i];
				sumC[i]=(sumC[i]+mod)%mod;
				// update
				c[i][x/i]=c[i][x/i]*y%mod;
				sumC[i]+=c[i][x/i];
				sumC[i]%=mod;
				ans+=((phi[i]*c[i][x/i]%mod)*sumR[i])%mod;
				ans%=mod;
				
				if(i*i!=x){
					int j=x/i;
					ans-=((phi[j]*c[j][x/j]%mod)*sumR[j])%mod;
					ans=(ans+mod)%mod;
					sumC[j]-=c[j][x/j];
					sumC[j]=(sumC[j]+mod)%mod;
					// update
					c[j][x/j]=c[j][x/j]*y%mod;
					sumC[j]+=c[j][x/j];
					sumC[j]%=mod;
					ans+=((phi[j]*c[j][x/j]%mod)*sumR[j])%mod;
					ans%=mod;
				}
			}
		}
		cout<<ans<<"\n";
	}
}
signed main(){
	freopen("1.in","r",stdin);
	freopen("mycode.out","w",stdout);
	ios_base::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	Euler(100000);
	int t=1;
	//cin>>t;
	while(t--){
		solve();
	}
}
posted @   liyishui  阅读(48)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示