大家好,我們選擇的是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\n", primecnt[query] - primecnt[int(sqrt(query))] + 1);
}
return 0;
}
今天的文章就到這裡,衷心祝願大家每天都有所收穫。如果還喜歡今天的內容的話,請來一個三連支援吧~(點贊、關注、轉發)
{{uploading-image-742109.png(uploading...)}}