大家好,我们选择的是Bubble Cup比赛Div2场次的J题,不用问我Bubble Cup是什么比赛,我也不清楚。总之是一场算法比赛就是了。可能是这个比赛知名度比较低吧,参与的人数也不是很多,我们选择了一道中等通过人数的J题,作为今天的题目。
链接:https://codeforces.com/contest/1424/problem/J
这题非常不错,是一道质量很高的数学题,也很符合我的胃口。因为没有太多的trick,有的只有思维和逻辑的碰撞。
题意
我们都知道对于两个数a和b来说,我们可以很容易求到它们的最大公约数。我们假设a和b的最大公约数是k,如果 这三个数的长度可以构成一个合法的三角形,那么我们就认为a和b是互相友好的。
对于一个合法三角形而言,我们假设它的三条边长度分别是a,b,c,必须要有a + b > c, a + c > b, b + c > a,也就是任意两边之和大于第三边。这个应该是小学数学教的内容,大家应该都很了解。
如果在一个集合当中,某一个数找不到友好的数,那么就认为它是孤独的。现在我们给定一系列的n,表示1-n的自然数构成的集合,我们要求的是在这个集合当中孤独的数的数量。
样例
首先给定一个t(),表示测试数据的数量。
接着给定一行t个整数,表示不同的n()。对于每一个n,输出一个整数,表示1-n的自然数组成的集合当中孤独的数的数量。
n=5时,孤独数分别是1、3、5。n=10时,孤独的数分别是1、5、7.
题解
由于n的范围是1e6,所以是不可能接受的算法的,因此我们不可能枚举所有两个数构成的组合情况。所以暴力求解是行不通的,我们必须分析题目,得出其他的结论从而来简化问题。
首先我们很容易发现,1一定是孤独的。原因也简单,对于任意自然数x,它与1的最大公约数都是1。那么带来的结果就是1、1和x。由于x不能和1重复,所以x最小是2,但即使是2,也不满足1+1>2,所以一定无法构成三角形。所以不论n是多少,1一定都是孤独的。
另外比较容易想到的点就是互质的情况,假设a和b互质,也就是说它们的最大公约数是1。这样我们得到的三角形的三边就是1、a、b。同样a和b不相等,所以a和b至少相差1,所以也无法构成三角形的三边。所以如果两个数互相互质,那么一定不是友好的。从这点回过头来看,其实1之所以是孤独的,正是因为它与其他所有数都互质。
从这点出发我们又可以想到什么呢?
对了,可以想到质数。质数与其他所有自然数的最大公约数要么是1,要么是它本身。对于最大公约数是1的情况,我们已经分析过了,下面就来分析一下最大公约数是它本身的情况。我们假设这个质数是x,另外一个数是b,由于x和b的最大公约数是x,说明b是x的倍数。那么我们将b表示成kx。
这样我们得到的三角形三边分别是x、1、k。我们可以得到三个限制条件:
第三条是显然的,我们可以忽略, 我们仔细看下前面两条。我们把它们联立可以得到:,那么x只有一种取值就是。那么k有没有限制呢?k也是有限制的,k不能随便取值,我们需要保证,也就是,即。
通过这么一串分析我们得到了一个结论,对于一个质数x,它想要不是孤独的,必须要满足。反之也可以得到当的时候,且x为质数的时候,x一定是孤独的。
现在我们讨论完了质数的情况,但是对于合数的情况我们还不知道,那么会不会存在一些合数也是孤独的呢?其实是不会的,我们可以这样来证明。我们假设我们讨论的某个合数m,既然它是合数,那么它一定可以分解质因数。并且它一定可以分解出一个小于的质因数,证明过程也很简单,因为合数要么是平方数,要么拥有至少两个质因数。不论是这哪两种情况,只要它的质因数大于等于,那么它本身一定大于n。所以这就矛盾了。
既然合数一定可以找到一个小于的质因数,我们不妨假设这个质因数是x,合数m写成ax。a的范围应该是。那么我们要做的就是寻找一个数b,使得bx可以和ax构成友好,并且,且a和b互质,否则就不满足。
那么能不能找到这样的b呢?当然是可以的,而且非常非常简单。我们首先来分析一下我们要达成的条件,由于要使得三角形合法,我们需要达成的条件有4个:
通过前两个条件,我们可以得到b的范围。但是后面两个条件怎么办呢?其实很简单,我们可以分情况讨论,如果,那么我们可以选择b = a-1,这样的b一定满足条件。如果a < x,我们可以选择 b = x。由于x是质数,并且a < x,那么可以保证a和x一定互质。
这样,我们就证明了,所有的合数一定都不是孤独的,它们都可以找到自己的友好数。
所以,最终我们要求的就是大于的质数的数量,不过不要忘了再加上1,因为1也是满足条件的孤独数。由于n最大有,如果我们一个一个求质数肯定来不及,这里我们可以使用我们之前介绍过的埃式筛法来快速求取所有的质数。
还有一个问题是,我们要求某一个范围内的质数数量应该怎么办?其实很简单,我们只需要使用前缀和即可。
除了这些之外,这题还有一个坑点就是时间卡得很紧。以至于同样的算法Python实现的会超时,被逼无奈之下我只好用上了祖传的C++重写了一遍,总算是通过了。大家可以对比一下Python和C++的效率差距,还是挺可观的。
最后,附上代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
#include <cmath>
#include <cstdlib>
#include <string>
#include <map>
#include <set>
#include <algorithm>
#include "time.h"
#include <functional>
// 一些宏定义
#define rep(i,a,b) for (int i=a;i<b;i++)
#define Rep(i,a,b) for (int i=a;i>=b;i--)
#define foreach(e,x) for (__typeof(x.begin()) e=x.begin();e!=x.end();e++)
#define mid ((l+r)>>1)
#define lson (k<<1)
#define rson (k<<1|1)
#define MEM(a,x) memset(a,x,sizeof a)
#define L ch[r][0]
#define R ch[r][1]
using namespace std;
const int N=1000050;
const long long Mod=1000000007;
int t, query[N];
int isprime[N], primecnt[N];
// 埃式筛法
void eratosthenes() {
rep(i, 0, N) isprime[i] = 1;
rep(i, 2, N) {
if (isprime[i]) {
for (int j = i+i; j < N; j += i) {
isprime[j] = 0;
}
}
}
// 维护质数的前缀和
rep(i, 2, N) {
primecnt[i] = primecnt[i-1] + isprime[i];
}
}
int main() {
eratosthenes();
scanf("%d", &t);
rep(i, 0, t) {
int query;
scanf("%d", &query);
printf("%d
", primecnt[query] - primecnt[int(sqrt(query))] + 1);
}
return 0;
}
今天的文章就到这里,衷心祝愿大家每天都有所收获。如果还喜欢今天的内容的话,请来一个三连支持吧~(点赞、关注、转发)
{{uploading-image-742109.png(uploading...)}}