【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)\),有 \(\gcd(x,y)\neq1\),那必然至少会有点 \((x/\gcd(x,y),y/\gcd(x,y))\) 在连向它的线段上,就不行了。
那就变成了要 \(\gcd(x,y)=1\)。
那你可以枚举 \(x\),然后就变成了求 \(1\sim x\) 中与 \(x\) 互质的个数。
那就是欧拉函数了,这题要算很多个,可以用线性筛来做。
代码(ybt & luogu)
Tips
这里它网格的大小是 \(n\),\((0,0)\sim(n-1,n-1)\)。
一定要注意不是到 \((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)\sim(n,n)\)。
我们可以把 \(\varphi\) 函数先预处理出来,然后再预处理乘二之后的前缀和,然后你就可以 \(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;
}