一、递归问题(引子)
1.hanoi双塔问题
①经典hanoi双塔
给你n个盘子,3根柱子分别标为ABC,n个盘子从上到下大小递增,要求大盘不能放在小盘上面且每次只能动一个盘子
问:将盘子全部转移到C柱需要多少步
要使最大的盘子到C柱,较小的n-1个盘子必须先到B柱,大盘再到C柱,小盘再到C柱,即:
我们考虑这个式子的封闭形式
即:
这样我们就可以在 的复杂度内解决这个问题了
②复杂情况
如果每次转移不允许最大的盘子直接到目标柱呢
这样就需要把小盘子先移到目标柱,再将大盘过中继柱,小盘回原柱,大盘到目标柱,小盘到目标柱
可得:
类比上式可得封闭形式:
③扩展
m柱n盘hanoi塔问题求解
设有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个交点,每两个相邻交点之间的线段将原区域分为两份,另外两条射线各将原平面分为两份
则有:
即:
②角分割平面
问:n个角最多能将平面分割为几部分
每个角可看作两条相交直线去掉交点一侧后剩下的部分
即去掉的一侧不再能够分割平面,共少分割了2n个
可得:
即:
二、数论
1.整除
①定义
对于整数 ,若 且存在整数k,使得 ,我们就称m整除n,m是n的约数,n是m的倍数,记作
②性质
2.素数
①定义
一个大于1的整数,除了1与其自身外,不能被其他正整数整除的数叫作素数,否则叫作合数
②判定
方法1:枚举 的正整数,判定能否整除n,复杂度
方法2:枚举 的素因子,判定能否整除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;
}
欧拉筛 :
#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 个int范围的数 ,判定其为素数or合数
读入 时预处理
欧拉筛筛出 范围的质数
用 范围的质数试除
复杂度
#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;
}
⑥区间筛
给定 ,求 范围的素数
线性筛 范围素数,再对 埃氏筛即可
#include<cstdio>
#include<bitset>
#include<cmath>
using namespace std;
const int MAXN=1e5+5;
int L,R;
int p[MAXN],ans[MAXN];
bitset<MAXN*10> b;
int main()
{
scanf("%d%d",&L,&R);
int maxn=sqrt(R);
for(int i=2;i<=maxn;++i){
if(!b[i]) p[++p[0]]=i;
for(int j=1;j<=p[0]&&i*p[j]<=maxn;++j){
b[i*p[j]]=1;
if(i%p[j]==0) break;
}
}b.reset();
for(int i=1;i<=p[0];++i)
for(int j=L/p[i]*p[i];j<=R;j+=p[i]) if(j-L>=0) b[j-L]=1;
for(int i=L;i<=R;++i) if(!b[i-L]) ans[++ans[0]]=i;
for(int i=1;i<=ans[0];++i) printf("%d ",ans[i]);
return 0;
}
3.模运算
定义
4.最大公约数
①定义
能整除两个整数m,n的最大整数称为m,n的最大公约数,记作 或
②求解方法
欧几里得算法:
int gcd(int a,int b){return !b?a:gcd(b,a%b);}
复杂度:
③裴蜀定理
④算数基本定理
⑤其他定理
5.不定方程
①求解方法
扩展欧几里得:
int Exgcd(int a,int b,int &x,int &y)
{
if(!b) {x=1,y=0;return a;}
int d=Exgcd(b,a%b,y,x);
y-=a/b*x; return d;
}
复杂度:
②同余方程
求解: 的最小正整数解
即:
扩展欧几里得求解即可
#include<cstdio>
int a,b,x,y,c;
int Exgcd(int a,int b,int &x,int &y)
{
if(!b) {x=1,y=0;return a;}
int d=Exgcd(b,a%b,y,x);
y-=a/b*x; return d;
}
int main()
{
scanf("%d%d%d",&a,&b,&c);c%=b;
int d=Exgcd(a,b,x,y);
if(c%d==0) printf("%d\n",(x*(c/d)%b+b)%b);
else puts("No solution");
return 0;
}
③青蛙约会问题
扩欧解决
设
若 则有解,否则无解
最终答案即为
#include<cstdio>
#define int long long
int x,y,m,n,L,a,b;
int Exgcd(int a,int b,int &m,int &n)
{
if(!b) {m=1,n=0;return a;}
int d=Exgcd(b,a%b,n,m);
n-=a/b*m; return d;
}
main()
{
scanf("%lld%lld%lld%lld%lld",&x,&y,&m,&n,&L);
a=m-n;
int d=Exgcd(a,L,m,n);L/=d;L=L>0?L:-L;
m=(m%L+L)%L;
if((y-x)%d!=0) puts("Impossible");
else printf("%lld\n",(m*(((y-x)/d)%L)%L+L)%L);
return 0;
}
④荒岛野人问题
暴力枚举M与每一对野人,扩欧判断每一对相遇时间与寿命的大小,如果相遇时间都大于寿命,则M满足要求
相遇时间计算方式与上题一样
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define int long long
using namespace std;
int n,M=1,e,g;
int f[16][16];//[0][i]:num;[i][0]:n
int Exgcd(int a,int b,int &e,int &g)
{
if(!b) {e=1,g=0;return a;}
int d=Exgcd(b,a%b,g,e);
g-=a/b*e; return d;
}
bool check2(int x,int y)
{
int d=Exgcd(f[x][0]-f[y][0],M,e,g);
int c=f[0][y]-f[0][x];
int L=M/d;L=L>0?L:-L;
if(c%d!=0) return 1;
if((e*((c/d)%L+L)%L+L)%L<=f[x][y]) return 0;
return 1;
}
bool check1()
{
for(int i=1;i<n;++i)
for(int j=i+1;j<=n;++j)
if(!check2(i,j)) return 0;
return 1;
}
main()
{
scanf("%lld",&n);
for(int i=1;i<=n;++i) scanf("%lld%lld%lld",&f[0][i],&f[i][0],&f[i][i]),M=max(M,f[0][i]);
for(int i=1;i<n;++i)
for(int j=i+1;j<=n;++j)
f[i][j]=f[j][i]=min(f[i][i],f[j][j]);
for(;;++M){
if(check1()){
printf("%lld\n",M);
return 0;
}
}return 0;
}
6.乘法逆元
①定义
②存在性
③求解方式
扩展欧几里得解
#include<cstdio>
int a,b,x,y;
int Exgcd(int a,int b,int &x,int &y)
{
if(!b) {x=1,y=0;return a;}
int d=Exgcd(b,a%b,y,x);
y-=a/b*x; return d;
}
int main()
{
scanf("%d%d",&a,&b);
int d=Exgcd(a,b,x,y);
if(d!=1) puts("No solution");
else printf("%d\n",(x%b+b)%b);
return 0;
}
m为质数且n
#include<cstdio>
const int MAXN=1e7+5;
int n,p;
int inv[MAXN];
int main()
{
inv[1]=1;
scanf("%d%d",&n,&p);
for(int i=2;i<=n;++i) inv[i]=((-(p/i)*inv[p%i])%p+p)%p;
for(int i=1;i<=n;++i) printf("%d ",inv[i]);
return 0;
}
阶乘逆元线性求解(条件同上)
#include<cstdio>
const int MAXN=1e7+5;
int n,p,t=1,x,y;
int inv[MAXN];
int Exgcd(int a,int b,int &x,int &y)
{
if(!b) {x=1,y=0;return a;}
int d=Exgcd(b,a%b,y,x);
y-=a/b*x; return d;
}
int main()
{
scanf("%d%d",&n,&p);
for(int i=2;i<=n;++i) t=(t*i)%p;
Exgcd(t,p,x,y);
inv[n]=(x%p+p)%p;
for(int i=n-1;i;--i) inv[i]=inv[i+1]*(i+1)%p;
for(int i=1;i<=n;++i) printf("%d ",inv[i]);
return 0;
}
④费马小定理
7.中国剩余定理
①内容
对于模线性方程组:
设:
其最小非负整数解:
②实现
#include<cstdio>
const int MAXN=5005;
int n;
long long x,y,ans;
long long a[MAXN],m[MAXN],M[MAXN],inv[MAXN];
long long Exgcd(long long a,long long b,long long &x,long long &y)
{
if(!b) {x=1,y=0;return a;}
long long d=Exgcd(b,a%b,y,x);
y-=a/b*x; return d;
}
int main()
{
scanf("%d",&n);M[0]=1;
for(int i=1;i<=n;++i) scanf("%lld%lld",&a[i],&m[i]),M[0]*=m[i];
for(int i=1;i<=n;++i){
M[i]=M[0]/m[i];
Exgcd(M[i],m[i],x,y);
inv[i]=(x+M[0])%M[0];
}for(int i=1;i<=n;++i) ans=(ans+a[i]*M[i]%M[0]*inv[i]%M[0])%M[0];
printf("%lld\n",ans);
return 0;
}
③扩展
如果m不互质怎么办呢
联立
解得
此处 仅表示对k的拆分,实际上,作为不定方程的一组解,有:
代入
即
这样一步步合并下去就能解出来了
#include<cstdio>
const int MAXN=2005;
int n;
long long x,y;
long long a[MAXN],m[MAXN];
long long Exgcd(long long a,long long b,long long &x,long long &y)
{
if(!b) {x=1,y=0;return a;}
long long d=Exgcd(b,a%b,y,x);
y-=a/b*x; return d;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%lld%lld",&a[i],&m[i]);
for(int i=1;i<n;++i){
long long nw=a[i+1]-a[i];
long long d=Exgcd(m[i],m[i+1],x,y);
if(nw%d){
puts("No solution");
return 0;
}x=(x*nw/d%m[i+1]+m[i+1])%m[i+1];
x%=m[i+1]/d;
m[i+1]=m[i+1]/d*m[i];
a[i+1]=(x*m[i]+a[i])%m[i+1];
}printf("%lld\n",a[n]);
return 0;
}
8.数论分块
①求
最多有约 种取值
可以分块求和
#include<cstdio>
long long n,sum;
int main()
{
scanf("%lld",&n);
for(long long l=1,r;l<=n;l=r+1){
r=n/(n/l);sum+=(n/l)*(r-l+1);
}printf("%lld\n",sum);
return 0;
}
复杂度
②求
观察到商一致的区间 内 构成公差为 的等差数列
分块等差数列求和即可
#include<cstdio>
long long n,sum;
int main()
{
scanf("%lld",&n);
for(long long l=1,r;l<=n;l=r+1){
r=n/(n/l);
sum+=(r-l+1)*(n%l+n%r)>>1;
}printf("%lld\n",sum);
return 0;
}
复杂度
③求
我们先不管 ,发现剩下的部分可以用乘法分配律在 的复杂度内计算
我们再看 的部分
显然在商一定的时候这段区间对答案的贡献是两个等差数列对应项相乘取相反数
设首项分别为 ,公差分别为
则区间的贡献为
套上取模就可以了(虽然这样代码就没法看了
小技巧:取模时模上6MOD就可以保留因子2和3了
#include<cstdio>
#include<algorithm>
using namespace std;
const int MOD=19940417,MOD2=MOD*6;
long long n,m,sum1,sum2;
int main()
{
scanf("%lld%lld",&n,&m);
for(long long l=1,r;l<=n;l=r+1){
r=n/(n/l);
sum1+=(r-l+1)*(n%l+n%r)>>1;
sum1%=MOD;
}
for(long long l=1,r;l<=m;l=r+1){
r=m/(m/l);
sum2+=(r-l+1)*(m%l+m%r)>>1;
sum2%=MOD;
}
sum1=sum1*sum2%MOD;sum2=0;
for(long long l=1,r;l<=min(n,m);l=r+1){
r=min(n/(n/l),min(m/(m/l),min(n,m)));
long long a=n%l,b=m%l;
if(l==r) sum2+=a*b%MOD;
else{
long long c=(a-n%r)/(r-l)%MOD,d=(b-m%r)/(r-l)%MOD;
sum2+=(r-l+1)*a%MOD*b%MOD
-(r-l)*(r-l+1)/2%MOD*(b*c%MOD+a*d%MOD)%MOD
+(r-l)*(r-l+1)%MOD2*(r*2-l*2+1)%MOD2/6*c%MOD*d%MOD;
}sum2%=MOD;
}sum1-=sum2;
printf("%lld\n",(sum1+MOD)%MOD);
return 0;
}
9.数论函数
①欧拉函数
可以线性筛求出1~n的欧拉函数
#include<cstdio>
#include<bitset>
using namespace std;
const int MAXN=5e5+5;
int n;
int phi[MAXN*10],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,phi[i]=i-1;
for(int j=1;j<=p[0]&&i*p[j]<=n;++j){
b[i*p[j]]=1;
if(i%p[j]) phi[i*p[j]]=phi[i]*(p[j]-1);
else {phi[i*p[j]]=phi[i]*p[j];break;}
}
}for(int i=1;i<=n;++i) printf("%d ",phi[i]);
return 0;
}
②欧拉定理
③扩展欧拉定理
④莫比乌斯函数
莫比乌斯函数同样是积性函数,所以也可以线性筛
#include<cstdio>
#include<bitset>
using namespace std;
const int MAXN=5e5+5;
int n;
int p[MAXN],mu[MAXN*10];
bitset<MAXN*10> b;
int main()
{
scanf("%d",&n);
mu[1]=1;
for(int i=2;i<=n;++i){
if(!b[i]) p[++p[0]]=i,mu[i]=-1;
for(int j=1;j<=p[0]&&i*p[j]<=n;++j){
b[i*p[j]]=1;
if(i%p[j]) mu[i*p[j]]=-mu[i];
else {mu[i*p[j]]=0;break;}
}
}for(int i=1;i<=n;++i) printf("%d ",mu[i]);
return 0;
}
同时该函数有一性质
令
则当且仅当 时 ,其余情况
证明:
时, ,故
此时令
则
那么总贡献为
接下来我们需要证明这个式子值为0
这等价于证明杨辉三角第(s+1)层上奇偶数位的数之和相等
而杨辉三角奇数位的数之和=上一层的所有数之和=其偶数位的数之和
故此时
综上,命题得证
⑤莫比乌斯反演
有了上面的性质,我们可以得出下面两条结论
我们证明第一个:
由乘法分配律可得
由上一框证得的性质可知,当且仅当d=n时 ,其他情况该式值为0
即
得证
同理可证第二个式子成立
10.习题
①
线性筛欧拉函数求和即可
#include<cstdio>
#include<bitset>
using namespace std;
const int MAXN=1e7+5;
int n;
int p[MAXN],phi[MAXN];
long long sum=1;
bitset<MAXN> b;
int main()
{
scanf("%d",&n);
for(int i=2;i<=n;++i){
if(!b[i]) p[++p[0]]=i,phi[i]=i-1;
sum+=phi[i]*2;
for(int j=1;j<=p[0]&&i*p[j]<=n;++j){
b[i*p[j]]=1;
if(i%p[j]) phi[i*p[j]]=phi[i]*phi[p[j]];
else {phi[i*p[j]]=phi[i]*p[j];break;}
}
}printf("%lld\n",sum);
return 0;
}
②
线性筛完对上题答案作个前缀和,枚举p求和即可
#include<cstdio>
#include<bitset>
using namespace std;
const int MAXN=1e7+5;
int n;
int p[MAXN],phi[MAXN];
long long sum[MAXN];
long long ans;
bitset<MAXN> b;
int main()
{
scanf("%d",&n);sum[1]=1;
for(int i=2;i<=n;++i){
if(!b[i]) p[++p[0]]=i,phi[i]=i-1;
sum[i]=sum[i-1]+phi[i]*2;
for(int j=1;j<=p[0]&&i*p[j]<=n;++j){
b[i*p[j]]=1;
if(i%p[j]) phi[i*p[j]]=phi[i]*phi[p[j]];
else {phi[i*p[j]]=phi[i]*p[j];break;}
}
}for(int i=1;i<=p[0];++i) ans+=sum[n/p[i]];
printf("%lld\n",ans);
return 0;
}
③
这个就需要莫比乌斯反演了
(即i,j,k都有因子x的组数)
则
那么
复杂度
#include<cstdio>
#include<bitset>
using namespace std;
const int MAXN=1e7+5;
int n;
long long ans;
int p[MAXN],mu[MAXN];
bitset<MAXN> b;
int main()
{
scanf("%d",&n);mu[1]=1;
for(int i=2;i<=n;++i){
if(!b[i]) p[++p[0]]=i,mu[i]=-1;
for(int j=1;j<=p[0]&&i*p[j]<=n;++j){
b[i*p[j]]=1;
if(i%p[j]) mu[i*p[j]]=-mu[i];
else {mu[i*p[j]]=0;break;}
}
}for(int i=1;i<=n;++i){
long long tmp=n/i;
tmp=tmp*tmp*tmp;
ans+=mu[i]*tmp;
}printf("%lld\n",ans);
return 0;
}
④
照着葫芦画王八
复杂度
#include<cstdio>
#include<bitset>
#include<algorithm>
using namespace std;
const int MAXN=1e7+5;
int x,n,m;
int p[MAXN],mu[MAXN];
long long sum;
bitset<MAXN> b;
int main()
{
scanf("%d%d%d",&x,&n,&m);mu[1]=1;
for(int i=2;i<=min(m,n);++i){
if(!b[i]) p[++p[0]]=i,mu[i]=-1;
for(int j=1;j<=p[0]&&i*p[j]<=min(m,n);++j){
b[i*p[j]]=1;
if(i%p[j]) mu[i*p[j]]=-mu[i];
else {mu[i*p[j]]=0;break;}
}
}for(int i=1;i*x<=min(n,m);++i) sum+=mu[i]*(n/(i*x))*(m/(i*x));
printf("%lld\n",sum);
return 0;
}
多组数据 ?
对 作前缀和,加个整除分块
复杂度
#include<cstdio>
#include<bitset>
#include<algorithm>
using namespace std;
const int MAXN=1e5+5;
int T,x,n,m;
long long p[MAXN],mu[MAXN],lis[MAXN];
long long sum;
bitset<MAXN> b;
int main()
{
mu[1]=lis[1]=1;scanf("%d",&T);
for(int i=2;i<=MAXN-5;++i){
if(!b[i]) p[++p[0]]=i,mu[i]=-1;
lis[i]=lis[i-1]+mu[i];
for(int j=1;j<=p[0]&&i*p[j]<=MAXN-5;++j){
b[i*p[j]]=1;
if(i%p[j]) mu[i*p[j]]=-mu[i];
else {mu[i*p[j]]=0;break;}
}
}while(T--){
scanf("%d%d%d",&n,&m,&x);
sum=0;n/=x;m/=x;
for(int l=1,r;l<=min(n,m);l=r+1){
r=min(n/(n/l),m/(m/l));
sum+=(lis[r]-lis[l-1])*(n/l)*(m/l);
}printf("%lld\n",sum);
}return 0;
}
⑤ 多组数据
前缀和,整除分块一下就好了
注意分段求解
#include<cstdio>
#include<bitset>
#include<algorithm>
using namespace std;
const int MAXN=5e4+5;
int T,x,a,b,c,d;
long long p[MAXN],mu[MAXN],lis[MAXN];
long long sum;
bitset<MAXN> bist;
int main()
{
mu[1]=lis[1]=1;scanf("%d",&T);
for(int i=2;i<=MAXN-5;++i){
if(!bist[i]) p[++p[0]]=i,mu[i]=-1;
lis[i]=lis[i-1]+mu[i];
for(int j=1;j<=p[0]&&i*p[j]<=MAXN-5;++j){
bist[i*p[j]]=1;
if(i%p[j]) mu[i*p[j]]=-mu[i];
else {mu[i*p[j]]=0;break;}
}
}while(T--){
scanf("%d%d%d%d%d",&a,&b,&c,&d,&x);
a=(a-1)/x;b/=x;c=(c-1)/x;d/=x;sum=0;
if(a>c){swap(a,c);swap(b,d);}
int l=1,r;
for(;l<=a;l=r+1){
int ra=a/(a/l),rb=b/(b/l),rc=c/(c/l),rd=d/(d/l);
r=min(ra,min(rb,min(rc,rd)));
sum+=(lis[r]-lis[l-1])*(b/l-a/l)*(d/l-c/l);
}for(;l<=min(b,c);l=r+1){
int rb=b/(b/l),rc=c/(c/l),rd=d/(d/l);
r=min(rb,min(rc,rd));
sum+=(lis[r]-lis[l-1])*(b/l)*(d/l-c/l);
}for(;l<=min(b,d);l=r+1){
int rb=b/(b/l),rd=d/(d/l);
r=min(rb,rd);
sum+=(lis[r]-lis[l-1])*(b/l)*(d/l);
}printf("%lld\n",sum);
}return 0;
}