一、递归问题(引子)
1.hanoi双塔问题
①经典hanoi双塔
给你n个盘子,3根柱子分别标为ABC,n个盘子从上到下大小递增,要求大盘不能放在小盘上面且每次只能动一个盘子
问:将盘子全部转移到C柱需要多少步
要使最大的盘子到C柱,较小的n-1个盘子必须先到B柱,大盘再到C柱,小盘再到C柱,即:
Fn=2Fn−1+1,F0=0 F n = 2 F n − 1 + 1 , F 0 = 0
我们考虑这个式子的封闭形式
Fn+1=2(Fn−1+1) F n + 1 = 2 ( F n − 1 + 1 )
Gn=Fn+1 G n = F n + 1
Gn=2Gn−1,G0=1 G n = 2 G n − 1 , G 0 = 1
Gn=2n G n = 2 n
即:
这样我们就可以在 O(logn) O ( log n ) 的复杂度内解决这个问题了
②复杂情况
如果每次转移不允许最大的盘子直接到目标柱呢
这样就需要把小盘子先移到目标柱,再将大盘过中继柱,小盘回原柱,大盘到目标柱,小盘到目标柱
可得: Fn=3Fn−1+2 F n = 3 F n − 1 + 2
类比上式可得封闭形式:
③扩展
m柱n盘hanoi塔问题求解
设有i个盘子,j个柱子时的答案为 f[i][j] f [ i ] [ j ] ,则有:
即:将k个盘子拿走,剩余盘子拿到目标柱,再转移k个盘子,组合的最小值即为所求
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN=2005,INF=0x7f;
int n,m;
int f[MAXN][MAXN];
int main()
{
scanf("%d%d",&n,&m);
for(int i=2;i<=m;++i) f[1][i]=1;
for(int i=3;i<=m;++i) f[2][i]=3;
for(int i=2;i<=n;++i) f[i][3]=f[i-1][3]*2+1;
for(int j=4;j<=m;++j){
for(int i=3;i<=n;++i){
int minn=0x7fffffff;
for(int k=1;k<i;++k){
minn=min(minn,f[k][j]*2+f[i-k][j-1]);
}f[i][j]=minn;
}
}printf("%d\n",f[n][m]);
return 0;
}
2.平面分割问题
①直线分割平面
问:n条直线最多将平面分割为几部分
我们考虑交点对答案的影响
第i条直线最多与其他直线形成i-1个交点,每两个相邻交点之间的线段将原区域分为两份,另外两条射线各将原平面分为两份
则有: Fn=Fn−1+n F n = F n − 1 + n
即:
②角分割平面
问:n个角最多能将平面分割为几部分
每个角可看作两条相交直线去掉交点一侧后剩下的部分
即去掉的一侧不再能够分割平面,共少分割了2n个
可得: Gn=F2n−2n G n = F 2 n − 2 n
即:
二、数论
1.整除
①定义
对于整数 n,m n , m ,若 m≠0 m ≠ 0 且存在整数k,使得 km=n k m = n ,我们就称m整除n,m是n的约数,n是m的倍数,记作 m|n m | n
②性质
- a|a a | a
- (a|b)∧(b|a)⇒a=±b ( a | b ) ∧ ( b | a ) ⇒ a = ± b
- (a|b)∧(b|c)⇒a|c ( a | b ) ∧ ( b | c ) ⇒ a | c
- (b≠0)∧(a|b)⇒|a|≤|b| ( b ≠ 0 ) ∧ ( a | b ) ⇒ | a | ≤ | b |
- a|b⇔am|bm a | b ⇔ a m | b m
- (a|b)∧(a|c)⇔a|(bx+cy) ( a | b ) ∧ ( a | c ) ⇔ a | ( b x + c y )
2.素数
①定义
一个大于1的整数,除了1与其自身外,不能被其他正整数整除的数叫作素数,否则叫作合数
②判定
方法1:枚举 2 n−−√ 2 n 的正整数,判定能否整除n,复杂度 O(n−−√) O ( n )
方法2:枚举 2 n−−√ 2 n 的素因子,判定能否整除n,复杂度 O(n√logen) O ( n l o g e n )
③约数个数
对于整数
由乘法原理可得其约数个数为:
④筛法求素数
埃氏筛 O(nlnlnn) O ( n ln ln n ) :
#include<cstdio>
#include<bitset>
using namespace std;
const int MAXN=1e6;
int n,p[MAXN];
bitset<MAXN*10> b;
int main()
{
scanf("%d",&n);
for(int i=2;i<=n;++i){
if(!b[i]){
p[++p[0]]=i;
for(int j=i+i;j<=n;j+=i) b[j]=1;
}
}for(int i=1;i<=p[0];++i) printf("%d ",p[i]);
return 0;
}
欧拉筛 O(n) O ( n ) :
#include<cstdio>
#include<bitset>
using namespace std;
const int MAXN=5e6+5;
int n;
int p[MAXN];
bitset<MAXN*10> b;
int main()
{
scanf("%d",&n);
for(int i=2;i<=n;++i){
if(!b[i]) p[++p[0]]=i;
for(int j=1;j<=p[0]&&p[j]*i<=n;++j){
b[i*p[j]]=1;
if(i%p[j]==0) break;
}
}for(int i=1;i<=p[0];++i) printf("%d ",p[i]);
return 0;
}
⑤应用
给定n (n<10000) ( n < 10000 ) 个int范围的数 ai a i ,判定其为素数or合数
读入 ai a i 时预处理 amax a m a x
欧拉筛筛出 [1,amax−−−−√] [ 1 , a m a x ] 范围的质数
用 [2,ai−−√] [ 2 , a i ] 范围的质数试除
复杂度 O(namax√lnamax) O ( n a m a x ln a m a x )
#include<cstdio>
#include<bitset>
#include<cmath>
using namespace std;
const int MAXN=1e4+5;
int n;
int a[MAXN],p[MAXN];
bitset<MAXN*10> b;
inline int max(int a,int b){
return a>b?a:b;}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%d",&a[i]),a[0]=max(a[0],a[i]);
a[0]=sqrt(a[0]);
for(int i=2;i<=a[0];++i){
if(!b[i]) p[++p[0]]=i;
for(int j=1;j<=p[0]&&i*p[j]<=a[0];++j){
b[i*p[j]]=1;
if(i%p[j]==0) break;
}
}for(int i=1;i<=n;++i){
bool fl=1;
for(int j=1;j<=p[0]&&p[j]*p[j]<=a[i];++j){
if(a[i]%p[j]==0){fl=0;break;}
}if(fl) puts("Yes");
else puts("No");
}return 0;
}
⑥区间筛
给定 L,R(1≤L≤R≤109,R−L<105) L , R ( 1 ≤ L ≤ R ≤ 10 9 , R − L < 10 5 ) ,求 [L,R] [ L , R ] 范围的素数
线性筛 [1,R−−√] [ 1 , R ] 范围素数,再对 [L,R]