【ybt金牌导航8-4-2】【luogu P2158】【POJ 3090】可见点数 / 仪仗队 / Visible Lattice Points

可见点数 / 仪仗队 / Visible Lattice Points

题目链接:ybt金牌导航8-4-2 / luogu P2158 / POJ 3090

题目大意

有一个网格,然后可以从 (0,0) 向其它整数点画一条线,但是中间不能经过其它整数点。

问你可以画多少条线。

思路

在这里插入图片描述
我们考虑根据这个图,看看能不能发现什么。

然后你会发现它有对称性,就是左下-右上的线段是对称轴,它两边线段的分布情况和个数都对称。
然后它这条线上也有一个符合的线。

那你可以只求一种一边的个数,然后乘二加一,就是答案。

然后你考虑一边的怎么弄。
那你想到,如果不满足的线,它是怎么样的。
可以想到,如果一个点 ( x , y ) (x,y) (x,y),有 gcd ⁡ ( x , y ) ≠ 1 \gcd(x,y)\neq1 gcd(x,y)=1,那必然至少会有点 ( x / gcd ⁡ ( x , y ) , y / gcd ⁡ ( x , y ) ) (x/\gcd(x,y),y/\gcd(x,y)) (x/gcd(x,y),y/gcd(x,y)) 在连向它的线段上,就不行了。

那就变成了要 gcd ⁡ ( x , y ) = 1 \gcd(x,y)=1 gcd(x,y)=1
那你可以枚举 x x x,然后就变成了求 1 ∼ x 1\sim x 1x 中与 x x x 互质的个数。
那就是欧拉函数了,这题要算很多个,可以用线性筛来做。

代码(ybt & luogu)

Tips

这里它网格的大小是 n n n ( 0 , 0 ) ∼ ( n − 1 , n − 1 ) (0,0)\sim(n-1,n-1) (0,0)(n1,n1)
一定要注意不是到 ( n , n ) (n,n) (n,n),它是小于而不是小于等于。

#include<cstdio>
#define ll long long

using namespace std;

int n, phi[40001], pr[40001];
bool np[40001];
ll ans;

int main() {
	scanf("%d", &n);
	
	phi[1] = 1;//求出 1~n 每个数的 phi 值
	for (int i = 2; i < n; i++) {
		if (!np[i]) {
			phi[i] = i - 1;
			pr[++pr[0]] = i;
			
		}
		for (int j = 1; j <= pr[0] && pr[j] * i < n; j++) {
			np[i * pr[j]] = 1;
			if (i % pr[j] == 0) phi[i * pr[j]] = phi[i] * pr[j];//不止一个这样的因子,只有第一个要减一
				else phi[i * pr[j]] = phi[i] * (pr[j] - 1);
			if (i % pr[j] == 0) break;//第一次出现,要减一
		}
	}
	
	for (int i = 1; i < n; i++)//对称性,乘二
		ans += 2ll * phi[i];
	ans++;//中间的那一条
	
	printf("%lld", ans);
	
	return 0;
}

代码(POJ)

Tips

这道题有多组数据,而且是 ( 0 , 0 ) ∼ ( n , n ) (0,0)\sim(n,n) (0,0)(n,n)

我们可以把 φ \varphi φ 函数先预处理出来,然后再预处理乘二之后的前缀和,然后你就可以 O ( 1 ) O(1) O(1) 处理询问了。

//由于思路相同,代码实现也差不多,就不再作注释了
#include<cstdio>
#define ll long long

using namespace std;

ll T, n, pr[40001];
bool np[40001];
ll phi[40001];

int main() {
	phi[1] = 1;
	for (ll i = 2; i <= 40000; i++) {
		if (!np[i]) {
			phi[i] = i - 1;
			pr[++pr[0]] = i;
		}
		for (ll j = 1; j <= pr[0] && pr[j] * i < 40000; j++) {
			np[i * pr[j]] = 1;
			if (i % pr[j] == 0) phi[i * pr[j]] = phi[i] * pr[j];
				else phi[i * pr[j]] = phi[i] * (pr[j] - 1ll);
			if (i % pr[j] == 0) break;
		}
	}
	
	for (ll i = 1; i <= 40000; i++)
		phi[i] = phi[i - 1] + phi[i] * 2ll;
	
	
	scanf("%lld", &T);
	
	for (ll i = 1; i <= T; i++) {
		scanf("%lld", &n);
		printf("%lld %lld %lld\n", i, n, phi[n] + 1);
	}
	
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值