[NOI2016] 循环之美

VII.[NOI2016] 循环之美

依据小学数论知识,我们要求

i=1nj=1m[gcd(i,j)=1][gcd(j,k)=1]

因为后面的 k 是个常数,所以我们就想把它搞出来。

i=1nj=1m[gcd(i,j)=1][gcd(j,k)=1]=i=1nj=1m[gcd(i,j)=1]d|j,d|kμ(d)=d|kμ(d)i=1nj=1m/d[gcd(i,jd)=1]=d|kμ(d)i=1nj=1m/d[gcd(i,j)=1][gcd(i,d)=1]

推到这步时,我们发现它好像跟我们最开头的式子莫名相似。

因此我们设原式为 f(n,m,k),则我们这里后面的式子好像就是 f(m/d,n,d)

因为每次都会除掉一个 d,而 nm 是轮流颠倒着除的,所以复杂度应该有个 logmlogn。又因为我们要暴力枚举 k 的所有因数,所以又乘上一个 k。跑到 k=1 的层之后又要求 i=1nj=1m[gcd(i,j)=1]=d=1min(n,m)μ(d)ndmd,因此还要上杜教筛。

但实际上,这个算法复杂度不是很优,2s内卡过不去,权当参考。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=20000000;
int pri[20010000],mu[20010000];
void sieve(){
	mu[1]=1;
	for(int i=2;i<=N;i++){
		if(!pri[i])pri[++pri[0]]=i,mu[i]=-1;
		for(int j=1;j<=pri[0]&&i*pri[j]<=N;j++){
			pri[i*pri[j]]=true;
			if(i%pri[j])mu[i*pri[j]]=-mu[i];
			else break;
		}
	}
	for(int i=1;i<=N;i++)mu[i]+=mu[i-1];
}
unordered_map<int,int>mp;
int MU(int n){
	if(n<=N)return mu[n];
	if(mp.find(n)!=mp.end())return mp[n];
	int ret=1;
	for(int l=2,r;l<=n;l=r+1)r=n/(n/l),ret-=(r-l+1)*MU(n/l);
	return mp[n]=ret;
}
ll func(int n,int m,int k){
	if(!n||!m)return 0;
	if(k==1){
		ll ret=0;
		for(int l=1,r;l<=min(n,m);l=r+1)r=min(n/(n/l),m/(m/l)),ret+=1ll*(n/l)*(m/l)*(MU(r)-MU(l-1));
		return ret;
	}
	ll ret=0;
	for(int d=1;d*d<=k;d++){
		if(k%d)continue;
		ret+=func(m/d,n,d)*(mu[d]-mu[d-1]);
		if(d*d!=k)ret+=func(m/(k/d),n,k/d)*(mu[k/d]-mu[k/d-1]);
	}
	return ret;
}
int n,m,k;
int main(){
	sieve();
	scanf("%d%d%d",&n,&m,&k);
	printf("%lld\n",func(n,m,k));
	return 0;
}

接下来我们反其道而行之,保留 k,处理 i

i=1nj=1m[gcd(i,j)=1][gcd(j,k)=1]=i=1nj=1md|i,d|jμ(d)[gcd(j,k)=1]=d=1min(n,m)μ(d)ndj=1,d|jm[gcd(j,k)=1]=d=1min(n,m)μ(d)ndj=1m/d[gcd(jd,k)=1]=d=1min(n,m)μ(d)nd[gcd(d,k)=1]j=1m/d[gcd(j,k)=1]

我们令 f(n) 表示 j=1n[gcd(j,k)=1]。当 jk 时,可以预处理;当 j>k 时,每 k 位一个循环节,且循环节内部大小为 φ(k)

于是有式子

f(n)=nkφ(k)+f(nmodk)

则现在 f 即可 O(1) 计算。我们即可将原式改写为 d=1min(n,m)μ(d)nd[gcd(d,k)=1]f(m/d)

d=1min(n,m)μ(d)nd[gcd(d,k)=1]f(m/d)=d=1min(n,m)ndf(m/d)μ(d)p|d,p|kμ(p)=p|kμ(p)d=1,p|dmin(n,m)ndf(m/d)μ(d)=p|kμ(p)d=1min(n,m)/pndpf(m/dp)μ(dp)

因为 μ 是积性函数,且有平方项的数的 μ 值为 0,故 μ(dp)=μ(d)μ(p)[gcd(d,p)=1],于是上式改写成 p|kμ(p)d=1min(n,m)/pndpf(m/dp)μ(d)μ(p)[gcd(d,p)=1]

p|kμ(p)d=1min(n,m)/pndpf(m/dp)μ(d)μ(p)[gcd(d,p)=1]=p|kμ2(p)d=1min(n,m)/pndpf(m/dp)μ(d)[gcd(d,p)=1]=p|kμ2(p)d=1min(n,m)/pn/pdf(m/pd)μ(d)[gcd(d,p)=1]

发现了什么?后面一大坨跟我们开始的式子 d=1min(n,m)μ(d)nd[gcd(d,k)=1]f(m/d) 极度相似。于是我们设 g(n,m,k)=d=1min(n,m)μ(d)nd[gcd(d,k)=1]f(m/d),则将最后的式子代进去,发现就是

p|kμ2(p)g(np,mp,p)

边界就是到 k=1 时,有 g(n,m,1)=d=1min(n,m)μ(d)ndf(m/d)μ 上杜教筛处理,剩下两个可以简单整除分块。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=20000000;
int pri[20010000],mu[20010000];
void sieve(){
	mu[1]=1;
	for(int i=2;i<=N;i++){
		if(!pri[i])pri[++pri[0]]=i,mu[i]=-1;
		for(int j=1;j<=pri[0]&&i*pri[j]<=N;j++){
			pri[i*pri[j]]=true;
			if(i%pri[j])mu[i*pri[j]]=-mu[i];
			else break;
		}
	}
	for(int i=1;i<=N;i++)mu[i]+=mu[i-1];
}
unordered_map<int,int>mp;
int MU(int n){
	if(n<=N)return mu[n];
	if(mp.find(n)!=mp.end())return mp[n];
	int ret=1;
	for(int l=2,r;l<=n;l=r+1)r=n/(n/l),ret-=(r-l+1)*MU(n/l);
	return mp[n]=ret;
}
int F(int n); 
ll func(int n,int m,int k){
	if(!n||!m)return 0;
	if(k==1){
		ll ret=0;
		for(int l=1,r;l<=min(n,m);l=r+1)r=min(n/(n/l),m/(m/l)),ret+=1ll*(n/l)*F(m/l)*(MU(r)-MU(l-1));
		return ret;
	}
	ll ret=0;
	for(int d=1;d*d<=k;d++){
		if(k%d)continue;
		if(mu[d]!=mu[d-1])ret+=func(n/d,m/d,d);
		if(d*d!=k&&mu[k/d]!=mu[k/d-1])ret+=func(n/(k/d),m/(k/d),k/d);
	}
	return ret;
}
int k,f[2010];
int F(int n){return (n/k)*f[k]+f[n%k];}
int n,m;
int main(){
	sieve();
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1;i<=k;i++)f[i]=f[i-1]+(__gcd(i,k)==1);
	printf("%lld\n",func(n,m,k));
	return 0;
}

本题是道难得的莫反好题,有多种思路,其中有三点我认为最为精妙:一是 [gcd(i,j)=1][gcd(j,k)=1][gcd(j,ik)=1],二是 μ(dp)=μ(d)μ(p)[gcd(d,p)=1],这两点在乘积项和项的乘积间架起了桥梁;还有一点就是函数式思考,遇见形式相似的东西就尝试设函数递归处理,在某些时候不失为一种手段。

posted @   Troverld  阅读(92)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示