算法之基础数论应用篇(一)
基礎數論應用篇
- 子集和
- 題目描述
- 篩質數
- 篩質數模板
- 歐拉篩
- 線性篩
- 哥德巴赫猜想
- 夏洛克和他的女朋友
- 二次篩法
- 分解質因數
- 試除法分解質因數
- 分解階乘質因子
- 快速冪
- 模板
- 快速冪
- 快速乘法
- 序列的第k個數(模板練習)
- 組合數 + 快速冪
- 約數個數
- 定理
- 輕拍牛頭(約數個數轉為倍數思路)
- 櫻花(約數個數+公式推導)
- 反素數(約數個數+數學知識)
子集和
題目描述
題目描述給你一個元素個數不超過30的整數集合,請你計算該集合所有子集的元素之和。例如集合{1,4},則該集合的子集共四個{{空集}、{1}、{4}、{1、4}},則子集的元素之和為1+4+1+4=10。輸入 第一行一個正整數n(1<=n<=30) 第二行n個大小在[1,1000000]范圍內的正整數輸出 輸出給定集合所有子集的元素之和 樣例輸入 2 1 4 樣例輸出 10題目思路及代碼
直接用結論元素之和乘以2的n-1次方。下面我們來講講為什么有這個結論:
我們依次考慮在每一個數在子集中出現的個數:
篩質數
篩質數模板
歐拉篩
void init(){is_prime[0] = is_prime[1] = 1;for(int i = 2 ; i <= N ; ++i)if(!is_prime[i]){p[cnt++] = i;for(int j = i; j <= N/i ; j ++)is_prime[j*i] = 1;} }線性篩
這里我要講一下線性篩的思路,因為在線性篩的時候有一些特殊的妙用。
即使在優化后(從x2開始), Eratosthenes 篩法仍然會重復標記合數。例如12既會被2又會被3標記,在標記2的倍數時,12=62,在標記3的倍數時,12=43。其根本原因是我們沒有確定出唯一的產生12的方式。
線性篩法通過“從大到小累積質因子”的方式標記每個合數,即讓12只有322一種產生方式。**設數組v記錄每個數的最小質因子,**我們按照以下步驟維護v。1.依次考慮2~N之間的每一個數i。
2.若v[i]=i,說明i是質數,把它保存下來。
3.掃描不大于v[i]的每個質數p,令v[ip]=p。也就是在i的基礎上累積一個質因子p。因為p≤v[i],所以p就是合數ip的最小質因子。
用處
- 篩素數
- 求每一個數的最小質因子
代碼:
void pries(){is_prime[0] = is_prime[1] = 1;memset(v , 0 ,sizeof v); // 最小質因子for(int i = 2 ; i <= N ; ++i){if(!is_prime[i]){v[i] = i ;p[++cnt] = i;}//給當前的數i乘上一個質因子for(int j = 1 ; j <= cnt ; ++j){//如果i有比p[j]更小的質因子,或者超過N的范圍,停止循環if(p[j] > v[i] || p[j] > N/i)break;// p[j] 是合數 i * p[j] 的最小質因子v[i*p[j]] = p[j];}} }哥德巴赫猜想
題目轉送門
解題思路:
本題我們在進行素數篩之后,由到1e6 的質數大約有1e5個,而本題是多組測試數據。因此對于每一個n,我們不能從頭或者開始找,而本題說了找的是差值最大的,因此我們可以前通過二分找到在質數數組中第一個大于n的位置開始往后找。
同時雖然哥德巴赫猜想還沒有被證明出來,但目前還沒有反例,因此答案肯定是存在的。
代碼:
夏洛克和他的女朋友
題目轉送門
解題思路:
這題很巧妙,同時我們注意條件使得一件珠寶的價格是另一件珠寶的價格的質因子時,也就是說不相同的兩個另一個一定會質數。那么如果我們將質數都染成一種顏色,那么其余的合數都染成另外一種顏色即可。也就是最多需要兩個。這是因為質數之間肯定是不符合條件的,在合數中,因為條件說了與其不相同的一定是質數,因此也可以。
代碼:
#include<iostream> #include<algorithm> #include<cstring> using namespace std; const int N = 1e5 + 10; int n ; int is_prime[N] ,p[N]; int ans[N];void init(int n){for(int i = 2 ; i <= n ; ++i)if(!is_prime[i])for(int j = i ; j <= n / i ; ++j) is_prime[j*i] = 1; }int main(){cin>>n;init(n+1);bool flag;for(int i = 2 ; i <= n+1 ; ++i)if(!is_prime[i]) ans[i] = 1;else ans[i] = 2 ,flag = 1;if(flag )cout<<2<<endl;else cout<<1<<endl;for(int i = 2 ; i <= n + 1; ++i)cout<<ans[i]<<' ';return 0; }二次篩法
題目轉送門
解題思路:
出處
因為題目說明左右兩端之間的數據不多與1e6,同時為了避免內存爆炸。因此我們在進行二次篩的時候是將值向左偏移L個單位,那么這樣最多只有1e6。
代碼:
#include<iostream> #include<algorithm> #include<cmath> #include<cstring> using namespace std; typedef long long LL; const int N = 1e7+10; int L,U ; int l ,r; bool st[N]; int cnt , prime[N];void init(int n){memset(st , 0 ,sizeof st);cnt = 0;for(int i = 2 ; i <= n ; ++i)if(!st[i]){prime[++cnt] = i;for(int j = i ; j <= n/i ; ++j)st[i*j] = 1;} }int main(){while(cin>>L>>U){init(50010);memset(st , 0 , sizeof st);for(int i = 1 ; i <= cnt ; ++i){LL p = prime[i];for(LL j = max(2*p , (L + p - 1)/p * p) ; j <= U ; j += p)st[j-L] = true; }cnt = 0;for(int i = 0 ; i <= U - L ; ++i)if(!st[i] && i + L >= 2)prime[cnt++] = i + L;if(cnt < 2) puts("There are no adjacent primes.");else{int minp = 0 , maxp = 0;for(int i = 0 ; i + 1 < cnt ; ++i){int d = prime[i+1] - prime[i];if(d < prime[minp+1] - prime[minp]) minp = i;if(d > prime[maxp+1] - prime[maxp]) maxp = i;}printf("%d,%d are closest, %d,%d are most distant.\n",prime[minp] ,prime[minp+1] ,prime[maxp] ,prime[maxp+1]);} }return 0; }分解質因數
試除法分解質因數
分解質因數:
枚舉1~sqrt(n)范圍內的所有質因子p(建一個素數表list),判斷 p是否是n的因子(能否整除)
如果在上面的步驟結束后n仍然>1,說明n有且僅有一個大于sqrt(n)的質因子,這時需要把這個質因子加入ans結果數組,令其個數為1
至此,ans數組中存放的就是質因子分解的結果。
代碼:’
void get(int n ){for(int i = 2 ; i <= sqrt(n) ; ++i){if(n % i ){ans[++cnt] = i , c[cnt] = 0;while(n % i ) n /= i , c[cnt]++;}if( n > 1) ans[++cnt] = n , c[cnt]++; }分解階乘質因子
若把1~N每個數分別分解質因數,再把結果合并,時間復雜度過高,為 O(N), 顯然,N!的每個質因子都不會超過N,我們可以先篩選出1~N的每個質數p,然后考慮階乘N!中一共包含多少個質因子p。
N!中質因子p的個數就等于1?N1-N1?N每個數包含質因子p的個數之和。在1~N中,p的倍數,即至少包含1個質因子p的顯然有 [N/p][N/p][N/p] 個。而p2p2p2的倍數,即至少包含2個質因子p的有 [N/p2][N/p^2][N/p2]個。不過其中的1個質因子已經在[N/p]里統計過,所以只需要再統計第2個質因子,即累加上 [N/p2][N/p^2][N/p2], 而不是 。2?[N/p2][N/p^2][N/p2]。 綜上所述,N!中質因子p的個數為:
∣N/ρ∣+∣N/ρ2∣+∣N/ρ3∣+?+∣N/ρlog?pN∣∣N/ρ∣+∣N/ρ2∣+∣N/ρ3∣+?+∣N/ρ^{\log{p}{N}}∣∣N/ρ∣+∣N/ρ2∣+∣N/ρ3∣+?+∣N/ρlogpN∣
對于每個p,只需要O(1gB)的時間計算上述和式。故對整個N!分解質因數的時間復雜度為O(NlogN)。
在這里我們可以利用除法的思維來求解
這里隨帶講下
代碼:
// n! 中質因子p 個個數 int get(int n , int p){int res = 0;for(LL i = p ; i <= n ; i *= p)res += n/i;return res; }快速冪
模板
快速冪
至于快速冪的思路大家應該很熟悉了。這里就細講一下。為什么這樣實現。b & 1得到最低位的1 , b >>= 1可以去掉最低位的1. 我們知道快速冪是從小開始迭代起來的(底數跌打)。當系數為1的時候表示我們需要乘當前迭代到的值。至于系數是0的時候為1乘1沒有變化因此不用管。 同時還有一個重要原因是取余對于乘法有性質。(a * b) % p =( ( a % p )* ( b % p )) % p
LL power(int a , int b){LL ans = 1 ;while(b){if(b & 1) ans = (LL) ans * a % mod;a = (LL)a * a%mod;b >>= 1;}return ans; }快速乘法
我們有
a?b=a?ci?bk?1+a?cj?bk?2...+c0?a?20a * b = a * c_i * b ^{k-1} + a * c_j * b ^ {k - 2} ... + c_0 * a * 2^0a?b=a?ci??bk?1+a?cj??bk?2...+c0??a?20由于加法對于取余也有與乘法類似的性質。后一項與前一項的關系為a?2i=a?(2i?1)?2a * 2 ^i = a * ( 2^{i-1}) * 2a?2i=a?(2i?1)?2
序列的第k個數(模板練習)
代碼:
#include<iostream> #include<algorithm> using namespace std; const int mod = 200907;typedef long long LL;LL power(int a , int b){LL ans = 1 ;while(b){if(b & 1) ans = (LL) ans * a % mod;a = (LL)a * a%mod;b >>= 1;}return ans; }LL mul(int a , int b){LL ans = 0;while(b){if(b & 1) ans = (LL)ans + a %mod;a = (LL) a * 2 % mod;b >>= 1;}return ans; }int main(){int T;cin>>T;while(T--){int a ,b , c , k;cin>>a>>b>>c>>k;if(a + c == 2*b)cout<<( a + mul(b - a , k - 1)) % mod<<endl;elsecout<<(LL)a*power(b/a ,k - 1) % mod<<endl;}return 0; }組合數 + 快速冪
題目轉送門
解題思路:
同時對于結果,對于有一個額外的數乘上快速冪的結果我們需要對這個結果再來一次取模。對于相減可能得負數的情況,我們可以通過加mod 再取模解決;
代碼:
#include<iostream> #include<algorithm> using namespace std; const int mod = 100003 ;typedef long long LL; LL n , m ;LL power(LL a , LL b){LL ans = 1 % mod;while(b){if(b & 1) ans = (LL) ans * a % mod;a = (LL) a * a % mod;b >>= 1;}return ans ; }int main(){cin>>m>>n;cout<< ( power(m , n) - ((LL) m * power(m-1,n-1)) % mod + mod) % mod<<endl;return 0; }約數個數
定理
這就是一個排列組合問題。我們知道p和c后,那么pnp^npn一共能組成p0..pnp^0..p^np0..pn一共(n+1)種數。其中的每一個數就能和其余p和c組成的 (m+1)中數組合成一個新約數。因此由乘法原理,方案數為上面式子。
輕拍牛頭(約數個數轉為倍數思路)
題目轉送門
解題思路:
這題問的是一個數,其余的數是其約數的個數(不包括自己)
在這里由于數據過大,因此我們不能通過求約數的方法來求。對于某一個值i ,我們令其倍數加上其出現的次數就可以了。這個看似是n^2復雜度,其實與求1-N的約數個數一樣,只是NlongN。
代碼:
#include<iostream> #include<algorithm> #include<cstdio> using namespace std; const int N = 1E6 + 10;int n; int cnt[N] , a[N] , s[N];int main(){cin>>n;for(int i = 1 ; i <= n ; ++i)scanf("%d",&a[i]) , cnt[a[i]]++;for(int i = 1 ; i <= N ; ++i)for(int j = 1 ; j <= N / i ; ++j)s[j*i] += cnt[i];for(int i = 1 ; i <= n ; ++i)printf("%d\n",s[a[i]] - 1);return 0; }櫻花(約數個數+公式推導)
題目轉送門
解題思路:
針對有兩個變量得式子,我們可以試著將其中一個變量寫成只含義另一個變量的形式。那么題目就轉變成了,后邊式子有多少種選法使得,y滿足條件。
對于這個題目,化簡有
y=x(n!)x?n!y = \frac{x(n!)}{x - n!} \quad y=x?n!x(n!)?
對x進行變形,右分母的形式我們有
y=(x?n!+n!)(n!)x?n!=n!+n!2x?n!y = \frac{(x-n!+n!)(n!)}{x - n!} \quad = n! + \frac{n!^2}{x - n!} \quad y=x?n!(x?n!+n!)(n!)?=n!+x?n!n!2?
有y為正整數,n!也一定是正整數。那么這題就轉變為了x有多少種選法使得
n!2x?n!\frac{n!^2}{x - n!} \quad x?n!n!2?為正整數。能被n!2n!^2n!2整除的就是其約數,因為x取值為正整數集,因此分母的約數一定能表示出來。那么這題就變成了求n!2n!^2n!2中約數的個數。
我們對定理出發有
代碼:
#include<iostream> #include<algorithm> #include<cstring> using namespace std; const int N = 1E6 + 10 , mod = 1e9 + 7;int n ; int primes[N] , cnt; bool st[N];void init(int n ){for(int i = 2; i <= n ; ++i)if(!st[i]){primes[cnt++] = i;for(int j = 1 ; j <= n/i ; ++j) st[i*j] = 1;} }int get(int n , int p){int ans = 0;while(n){ans += n/p;n /= p;}return ans * 2; }int main(){cin>>n;int ans = 1;init(n);for(int i = 0 ; i < cnt && primes[i] <= n ; ++i)ans = (long long )ans * ( 1 + get(n , primes[i])) % mod;cout<<ans<<endl;return 0; }反素數(約數個數+數學知識)
題目轉送門
解題思路:
綜上所述,我們可以使用深度優先搜索(DFS),嘗試依次確定前10個質數的指數,并滿足指數單調遞減、總乘積不超過N,同時記錄約數的個數。
在這兩個限制條件下,搜索量實際上非常小。每當搜索出一個滿足條件的整數時,我們就按照引理1的結論更新答案,最終得到約數個數最多的數中最小的一個。
代碼:
#include<iostream> #include<algorithm> #include<cstring> #include<cmath> using namespace std; const int N = 1E5; typedef unsigned long long LL; int n; LL maxn = 0, ans ; int primes[11] = {2,3,5,7,11,13,17,19,23,29};void dfs(int s ,LL sum , int p , LL cnt){if(sum > n)return ;if(p == 10 && sum <= n){if(cnt == maxn)ans = min(ans , sum);if(cnt > maxn) ans = sum;maxn = max(maxn ,cnt);return ;}if(s == 0 ) return ;for(int i = s ; i >= 0 ; --i)dfs(s,sum * (LL)pow(primes[p],i) , p+1 , cnt * (i+1));}int main(){cin>>n;ans = N;dfs(9,1,0 , 1);cout<<ans<<endl;return 0; }總結
以上是生活随笔為你收集整理的算法之基础数论应用篇(一)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 校内训练赛题解第三篇
- 下一篇: 算法之数论应用篇(二)