POJ - 3090 Visible Lattice Points (数学,四种做法)

题目链接:点击这里

题目大意:
在这里插入图片描述
题目分析:
本题需要一个重要的转化:
仔细观察上图,我们会发现:当一个点的横坐标和纵坐标的 gcd ⁡ \gcd gcd 大于 1 1 1 时就会被遮挡,即 gcd ⁡ ( x , y ) ≠ 1 \gcd(x,y)≠1 gcd(x,y)=1 的点会被遮住
所以题目所求就是: 2 + ∑ i = 1 n ∑ j = 1 n [ gcd ⁡ ( i , j ) = 1 ] 2+\sum_{i=1}^n\sum_{j=1}^n[\gcd(i,j)=1] 2+i=1nj=1n[gcd(i,j)=1](其中2是x=0 || y=0的贡献)
所以便有了最直接的方法 1 1 1
我们可以把所有点对的 gcd ⁡ \gcd gcd 都求出来,记 k [ i ] [ j ] = 1 k[i][j]=1 k[i][j]=1 为其 gcd ⁡ ( i , j ) = 1 \gcd(i,j)=1 gcd(i,j)=1,其余情况 k [ i ] [ j ] = 0 k[i][j]=0 k[i][j]=0 ,此时题目所求就转化为了求有多少个 k [ i ] [ j ] = 1 k[i][j]=1 k[i][j]=1,我们直接对 k k k 数组做一次二维前缀和,然后就可以 O ( 1 ) O(1) O(1) 查询区域中1的个数了
总体时间复杂度为 𝑂 ( 𝑛 2 l o g ⁡ 𝑛 + 𝑡 ) 𝑂(𝑛^2 log⁡𝑛+𝑡) O(n2logn+t)

方法 2 2 2
我们构造一个数组 f [ i ] f[i] f[i] 表示 g c d ( x , y ) = 𝑖 gcd(x,y)=𝑖 gcd(x,y)=i 的坐标数
我们可以用公约数是 i i i 的坐标数 ( n / i ) ∗ ( n / i ) (n/i)*(n/i) (n/i)(n/i) 减去 ∑ k = 2 k ∗ i ≤ n f [ k ∗ i ] \sum_{k=2}^{k*i\le n}f[k*i] k=2kinf[ki]
所以求 f [ i ] f[i] f[i] 的时间复杂度为 𝑂 ( 𝑛 l o g ⁡ 𝑛 ) 𝑂(𝑛 log⁡𝑛) O(nlogn) (调和级数)
总体时间复杂度为 𝑂 ( 𝑡 ⋅ 𝑛 l o g ⁡ 𝑛 ) 𝑂(𝑡·𝑛 log⁡𝑛) O(tnlogn)
此方法的优点:代码量小,可处理横纵坐标长度不同的区域

方法 3 3 3
由于题目所求为: 2 + ∑ i = 1 n ∑ j = 1 n [ gcd ⁡ ( i , j ) = 1 ] 2+\sum_{i=1}^n\sum_{j=1}^n[\gcd(i,j)=1] 2+i=1nj=1n[gcd(i,j)=1]
∑ i = 1 n gcd ⁡ ( i , i ) = 1 \sum_{i=1}^n\gcd(i,i)=1 i=1ngcd(i,i)=1
所以 ∑ i = 1 n ∑ j = 1 n [ gcd ⁡ ( i , j ) = 1 ] \sum_{i=1}^n\sum_{j=1}^n[\gcd(i,j)=1] i=1nj=1n[gcd(i,j)=1]
= − ∑ i = 1 n gcd ⁡ ( i , i ) + ∑ i = 1 n ∑ j = 1 i [ gcd ⁡ ( i , j ) = 1 ] =-\sum_{i=1}^n\gcd(i,i)+\sum_{i=1}^n\sum_{j=1}^i[\gcd(i,j)=1] =i=1ngcd(i,i)+i=1nj=1i[gcd(i,j)=1]
= − 1 + ∑ i = 1 n ∑ j = 1 i [ gcd ⁡ ( i , j ) = 1 ] =-1+\sum_{i=1}^n\sum_{j=1}^i[\gcd(i,j)=1] =1+i=1nj=1i[gcd(i,j)=1]
= − 1 + ∑ i = 1 n φ ( i ) =-1+\sum_{i=1}^n\varphi(i) =1+i=1nφ(i)
我们可以用线性筛求处欧拉函数,然后对其求前缀和即可
总时间复杂度为 𝑂 ( 𝑛 + 𝑡 ) 𝑂(𝑛+𝑡) O(n+t)
n n n 很大时可以考虑用杜教筛优化求 ∑ i = 1 n φ ( i ) \sum_{i=1}^n\varphi(i) i=1nφ(i) 部分 到 O ( n 2 3 ) O(n^{\frac{2}{3}}) O(n32)

方法 4 4 4
∑ i = 1 N ∑ j = 1 N [ gcd ⁡ ( i , j ) = 1 ] \sum_{i=1}^N\sum_{j=1}^N[\gcd(i,j)=1] i=1Nj=1N[gcd(i,j)=1] 不难想到可以对其进行莫比乌斯反演(倍数反演) :
F ( n ) = ( ⌊ N n ⌋ ) 2 F(n)=(\lfloor \frac{N}{n} \rfloor)^2 F(n)=(nN)2 n ∣ gcd ⁡ ( i , j ) n|\gcd(i,j) ngcd(i,j) 的数量, f ( n ) f(n) f(n) gcd ⁡ ( i , j ) = n \gcd(i,j)=n gcd(i,j)=n 的数量,则有 F ( n ) = ∑ n ∣ d f ( d ) F(n)=\sum_{n|d}f(d) F(n)=ndf(d) ,反演得 f ( n ) = ∑ n ∣ d μ ( d n ) F ( d ) = ∑ n ∣ d μ ( d n ) ( ⌊ N d ⌋ ) 2 f(n) = \sum_{n|d}\mu(\frac{d}{n})F(d)=\sum_{n|d}\mu(\frac dn)(\lfloor \frac{N}{d} \rfloor)^2 f(n)=ndμ(nd)F(d)=ndμ(nd)(dN)2 ,
所以 f ( 1 ) = ∑ d = 1 N μ ( d ) ( ⌊ N d ⌋ ) 2 f(1)=\sum_{d=1}^N\mu(d)(\lfloor \frac{N}{d} \rfloor)^2 f(1)=d=1Nμ(d)(dN)2,线性筛求处 μ \mu μ 然后求前缀和,再套个整除分块即可,时间复杂度为 O ( n ) O(\sqrt n) O(n )

方法 1 1 1 代码如下:

//#pragma GCC optimize(2)
//#pragma GCC optimize("Ofast","inline","-ffast-math")
//#pragma GCC target("avx,sse2,sse3,sse4,mmx")
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<set>
#include<map>
#include<stack>
#include<queue>
// #include<unordered_map>
#define ll long long
#define inf 0x3f3f3f3f
#define Inf 0x3f3f3f3f3f3f3f3f
// #define int  ll
#define endl '\n'
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
using namespace std;
int read()
{
	int res = 0,flag = 1;
	char ch = getchar();
	while(ch<'0' || ch>'9')
	{
		if(ch == '-') flag = -1;
		ch = getchar();
	}
	while(ch>='0' && ch<='9')
	{
		res = (res<<3)+(res<<1)+(ch^48);//res*10+ch-'0';
		ch = getchar();
	}
	return res*flag;
}
const int maxn = 1e3+5;
const int mod = 1e9+7;
const double pi = acos(-1);
const double eps = 1e-8;
int n,m,k[maxn][maxn],sum[maxn][maxn],cas;
int gcd(int a,int b)
{
	return b ? gcd(b,a%b) : a;
}
int main()
{
	for(int i = 1;i < maxn;i++)
		for(int j = 1;j < maxn;j++) k[i][j] = gcd(i,j)==1 ? 1 : 0;
	for(int i = 1;i < maxn;i++)
		for(int j = 1;j < maxn;j++) sum[i][j] = k[i][j]+sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];
	int t = read();
	while(t--)
	{
		n = read();
		printf("%d %d %d\n",++cas,n,sum[n][n]+2);
	}
	return 0;
}

方法 2 2 2 代码如下:

//#pragma GCC optimize(2)
//#pragma GCC optimize("Ofast","inline","-ffast-math")
//#pragma GCC target("avx,sse2,sse3,sse4,mmx")
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<set>
#include<map>
#include<stack>
#include<queue>
// #include<unordered_map>
#define ll long long
#define inf 0x3f3f3f3f
#define Inf 0x3f3f3f3f3f3f3f3f
// #define int  ll
#define endl '\n'
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
using namespace std;
int read()
{
	int res = 0,flag = 1;
	char ch = getchar();
	while(ch<'0' || ch>'9')
	{
		if(ch == '-') flag = -1;
		ch = getchar();
	}
	while(ch>='0' && ch<='9')
	{
		res = (res<<3)+(res<<1)+(ch^48);//res*10+ch-'0';
		ch = getchar();
	}
	return res*flag;
}
const int maxn = 1e3+5;
const int mod = 1e9+7;
const double pi = acos(-1);
const double eps = 1e-8;
int n,m,f[maxn],cas;
int gcd(int a,int b)
{
	return b ? gcd(b,a%b) : a;
}
int main()
{
	int t = read();
	while(t--)
	{
		n = read();
		int res = n*n;
		for(int i = 1;i <= n;i++) f[i] = 0;
		for(int i = n;i >= 2;i--)
		{
			f[i] = (n/i)*(n/i);
			for(int j = 2*i;j <= n;j += i) f[i] -= f[j];
			res -= f[i];
		}
		printf("%d %d %d\n",++cas,n,res+2);
	}
	return 0;
}

方法 3 3 3 代码如下:

//#pragma GCC optimize(2)
//#pragma GCC optimize("Ofast","inline","-ffast-math")
//#pragma GCC target("avx,sse2,sse3,sse4,mmx")
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<set>
#include<map>
#include<stack>
#include<queue>
// #include<unordered_map>
#define ll long long
#define inf 0x3f3f3f3f
#define Inf 0x3f3f3f3f3f3f3f3f
// #define int  ll
#define endl '\n'
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
using namespace std;
int read()
{
	int res = 0,flag = 1;
	char ch = getchar();
	while(ch<'0' || ch>'9')
	{
		if(ch == '-') flag = -1;
		ch = getchar();
	}
	while(ch>='0' && ch<='9')
	{
		res = (res<<3)+(res<<1)+(ch^48);//res*10+ch-'0';
		ch = getchar();
	}
	return res*flag;
}
const int maxn = 1e3+5;
const int mod = 1e9+7;
const double pi = acos(-1);
const double eps = 1e-8;
int n,m,cas,cnt,pri[maxn],phi[maxn];
bool vis[maxn];
void init(int n)
{
	phi[1] = 1;
	for(int i = 2;i <= n;i++)
	{
		if(!vis[i]) pri[++cnt] = i,phi[i] = i-1;
		for(int j = 1;j<=cnt && i*pri[j]<=n;j++)
		{
			vis[i*pri[j]] = true;
			if(i%pri[j] == 0)
			{
				phi[i*pri[j]] = phi[i]*pri[j];
				break;
			}
			phi[i*pri[j]] = phi[i]*(pri[j]-1);
		}
	}
	for(int i = 2;i <= n;i++) phi[i] += phi[i-1];
}
int main()
{
	init(maxn-1);
	int t = read();
	while(t--)
	{
		n = read();
		printf("%d %d %d\n",++cas,n,1+2*phi[n]);
	}
	return 0;
}

方法 4 4 4 代码如下:

//#pragma GCC optimize(2)
//#pragma GCC optimize("Ofast","inline","-ffast-math")
//#pragma GCC target("avx,sse2,sse3,sse4,mmx")
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<set>
#include<map>
#include<stack>
#include<queue>
// #include<unordered_map>
#define ll long long
#define inf 0x3f3f3f3f
#define Inf 0x3f3f3f3f3f3f3f3f
// #define int  ll
#define endl '\n'
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
using namespace std;
int read()
{
	int res = 0,flag = 1;
	char ch = getchar();
	while(ch<'0' || ch>'9')
	{
		if(ch == '-') flag = -1;
		ch = getchar();
	}
	while(ch>='0' && ch<='9')
	{
		res = (res<<3)+(res<<1)+(ch^48);//res*10+ch-'0';
		ch = getchar();
	}
	return res*flag;
}
const int maxn = 1e3+5;
const int mod = 1e9+7;
const double pi = acos(-1);
const double eps = 1e-8;
int n,m,cas,cnt,pri[maxn],mu[maxn],sum[maxn];
bool vis[maxn];
void get_mu() //筛莫比乌斯函数,求需要的函数的前缀和
{
	mu[1] = vis[1] = 1;
	for(int i = 2;i < maxn;i++)
	{
		if(!vis[i])
		{
			mu[i] = -1;
			pri[++cnt] = i;
		}
		for(int j = 1;j <= cnt && i*pri[j] < maxn;j++)
		{
			vis[i*pri[j]] = true;
			if(i%pri[j] != 0) mu[i*pri[j]] -= mu[i];
			else break;
		}
	}
	for(int i = 1;i < maxn;i++)
		sum[i] = sum[i-1]+mu[i];
}
int main()
{
	get_mu();
	int t = read();
	while(t--)
	{
		n = read();
		int res = 1;
		for(int l = 1,r;l <= n;l = r+1)
		{
			r = (n/(n/l));
			res += (sum[r]-sum[l-1])*(n/l)*(n/l);
		}
		printf("%d %d %d\n",++cas,n,1+res);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值