【2019-总结】CSP2019考前复习——数论&数据结构

一、前言

不知不觉,离CSP比赛只有两天了...

赶紧复习一波(尴尬的是,老师列出的复习内容分有好多我都没有系统学O.O

复习计划:周四——数学相关;周五——数据结构相关

二、数学

之前自己写了一篇总结:https://blog.csdn.net/qq_36294918/article/details/87552138

这里只做重点知识的回顾与强调,应该会有许多补充


Part 1.两个“扩展”

1.Exgcd(扩展欧几里得算法)

先来谈谈【欧几里得算法】

个人觉得度娘的证明更让我能理解=。=:

//辗转相除法算最大公因数
int gcd(int a,int b)
{
	return b==0?a:gcd(b,a%b);
}
//(x,y)*[x,y]=x*y——>lcm(x,y)=x*y/gcd(x,y)
int lcm(int a,int b)
{
	return a/gcd(a,b)*b;
}

【扩展欧几里得】

为了介绍扩展欧几里得,我们先介绍一下贝祖定理:

           即如果a、b是整数,那么一定存在整数x、y使得ax+by=gcd(a,b)。

换句话说,如果ax+by=m有解,那么m一定是gcd(a,b)的若干倍。(可以来判断一个这样的式子有没有解)

有一个直接的应用就是 如果ax+by=1有解,那么gcd(a,b)=1;

要求出这个最大公因数gcd(a,b),我们最容易想到的就是古老悠久而又相当强大的辗转相除法:

    int gcd(int a,int b)
    {
        return b==0?a:gcd(b,a%b);
    }

但是,对于上面的式子ax+by=m来说,我们并不仅仅想要知道有没有解,而是想要知道在有解的情况下这个解到底是多少。

所以,扩展欧几里得

        当到达递归边界的时候,b==0,a=gcd(a,b) 这时可以观察出来这个式子的一个解:a*1+b*0=gcd(a,b),x=1,y=0,注意这时的a和b已经不是最开始的那个a和b了,所以我们如果想要求出解x和y,就要回到最开始的模样。

        初步想法:由于是递归的算法,如果我们知道了这一层和上一层的关系,一层一层推下去,就可以推到最开始的。类似数学上的数学归纳法。

        假设当前我们在求的时a和b的最大公约数,而我们已经求出了下一个状态:b和a%b的最大公因数,并且求出了一组x1和y1使得                          b*x1+(a%b)*y1=gcd

(注意在递归算法中,永远都是先得到下面一个状态的值)

这时我们可以试着去寻找这两个相邻状态的关系:

首先我们知道:a%b=a-(a/b)*b;带入:

b*x1 + (a-(a/b)*b)*y1

= b*x1 + a*y1 – (a/b)*b*y1

= a*y1 + b*(x1 – a/b*y1) = gcd   发现 x = y1 , y = x1 – a/b*y1

这样我们就得到了每两个相邻状态的x和y的转化,就可以在求gcd的同时对x和y进行求值了hiahia

参考博客:https://blog.csdn.net/destiny1507/article/details/81750874

int exgcd(int a,int b,int &x,int &y)//扩展欧几里得算法
{//ax+by=gcd(a,b)
    if(b==0)
    {
        x=1;y=0;
        return a;//到达递归边界开始向上一层返回 
    }
    int r=exgcd(b,a%b,x,y);
    //把x y变成上一层的
    int temp=y;    
    y=x-(a/b)*y;
    x=temp;
    return r;//得到a,b的最大公因数
}

2.Excrt(扩展中国剩余定理)

(大概看看吧,反正我也不懂=。=)

先谈谈【中国剩余定理】

•有物不知其数,三三数之剩二,五五数之剩三,七七数之剩二。问物几何?——《孙子算经》
•古代数学家给出了解决这个问题的口诀:三人同行七十希,五树梅花廿一支,七子团圆正半月,除百零五便得知
•由于2是5*7=35关于3的逆元,1是3*7=21关于5的逆元,1是3*5关于7的逆元
•“七十”=2*35,“廿一”=1*21,“正半月”=1*15,“百零五”=3*5*7
•所以通解即为(2*70+3*21+2*15)+105k
•最小的正整数解为23
【扩展中国剩余定理】

Part 2.数论常用知识/工具

1.线性筛

(一)欧拉筛法:线性筛

一年前不理解,一年后的自己居然一看就看懂了qwq!

对每个合数a×b,它会被每个质因数都筛去一遍
但我们只要用最小的质因数筛去就好了
为此,我们需要记录下所产生的全部素数,代码如下

void sieve(int n)
{
	for(int i=2;i<=n;i++)
	{
		if(!vis[i])
			prime[++cnt]=i,vis[i]=1;
		for(int j=1;j<=cnt&&i*prime[j]<=n;j++)
		{
			vis[i*prime[j]]=1;
			if(i%prime[j]==0)		
				break;
		}
	}
}

【核心】

if(i%prime[j]==0)		
	break;

如果i能整除primelist[k],
说明primelist[k]是i的因子,
所以primelist[k]也是i的任意倍数的因子。
所以primelist[k]也是i×primelist[x] (x>k)的因子。
考虑到primelist单增,对i×primelist[x],primelist[k]就是它的比primelist[x]更小的因子。
故不用考虑其后的质因子了。
(i×primelist[x]会被primelist[k]作为因子在i更大时被筛掉)

(二)欧拉筛法求约数个数d[n]

void sieve(int n)
{
        d[1]=1;
	for(int i=2;i<=n;i++)
	{
		if(!vis[i])
			prime[++cnt]=i,vis[i]=1,num[i]=1,d[i]=2;
		for(int j=1;j<=cnt&&i*prime[j]<=n;j++)
		{
			vis[i*prime[j]]=1;
			if(i%prime[j]==0)		
			{
				num[i*prime[j]]=num[i]+1;
				d[i*prime[j]]=d[i]/(num[i]+1)*(num[i]+2);
				break;
			}
			d[i*prime[j]]=d[i]*2;
			num[i*prime[j]]=1;
		}
	}
}

(三)欧拉筛法求约数和s[n]

void sieve(int n)
{
	s[1]=1;
	for(int i=2;i<=n;i++)
	{
		if(!vis[i])
			prime[++cnt]=i,vis[i]=1,psum[i]=s[i]=i+1;
		for(int j=1;j<=cnt&&i*prime[j]<=n;j++)
		{
			vis[i*prime[j]]=1;
			if(i%prime[j]==0)		
			{
				psum[i*prime[j]]=psum[i]*prime[j]+1;
				s[i*prime[j]]=s[i]/psum[i]*psum[i*prime[j]];
				break;
			}
			s[i*prime[j]]=s[i]*(prime[j]+1);
			psum[i*prime[j]]=1+prime[j];
		}
	}
}

2.积性函数

• 积性函数对于所有互质的整数a和b有性质f ( a · b ) = f ( a ) · f ( b )

其实自己不是很理解积性函数

敲黑板!!!下面内容十分重要!

本人垃圾的小学奥数功底:约数个数口诀:指数加一再相乘

对于“因数和定理”,我个人的理解是:

对于第k种因数,可以为pk^0,pk^1,pk^2.....pk^ak,每种因数都是这样,最后根据【乘法原理】全部乘起来就是总和了

3.逆元

这里有个神仙博客!非常详细:https://www.cnblogs.com/Judge/p/9383034.html#_lab2_1_0

(一)费马小定理求逆元

a^{^{-1}}=a^{n-2} % p(用此方法时,p一定要是质数!)

int quick_pow(int x,int p)
{
	int res=1;
	while(p)
	{
		if(p&1)
			res=(res*x)%mod;
		x=(x*x)%mod,p>>=1;
	}
	return res;
} 
int get_inv(int a)
{
	return quick_pow(a,mod-2);
}

(二)exgcd求逆元

  

#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int exgcd(int a,int b,int &x,int &y)//ax+by=c
{
	if(b==0)
	{
		x=1,y=0;
		return a;
	}
	int r=exgcd(b,a%b,x,y);
	int tmp=y;
	y=x-(a/b)*y;
	x=tmp;
	return r;
}
int inv(int a,int p)
{
	int d,x,y;
	d=exgcd(a,p,x,y);
	return d==1?(x+p)%p:-1;
}
int main()
{
	int a,p;
	scanf("%d%d",&a,&p);
	printf("%d\n",inv(a,p));
	return 0;
}

(三)递推法求逆元

//线性递推求逆元 
void get_inv1()
{
	inv[0]=inv[1]=1;
	for(int i=2;i<=n;i++)
		inv[i]=inv[mod%i]*(mod-mod/i)%mod;
}
//线性逆元求[阶乘逆元]法一  
void get_inv2()
{
	finv[0]=finv[1]=1;
	for(int i=2;i<=n;i++)
		finv[i]=finv[mod%i]*(mod-mod/i)%mod;
	for(int i=2;i<=n;i++)
		finv[i]=finv[i]*finv[i-1]%mod;
 } 
//线性逆元求[阶乘逆元]法二  
void get_inv3()
{
	fac[1]=finv[0]=1;
	for(int i=2;i<=n;i++)
		fac[i]=fac[i-1]*i%mod;
	finv[n]=quick_pow(fac[n],mod-2);
	for(int i=n-1;i>=1;i--)
		finv[i]=finv[i+1]*(i+1)%mod;
} 

【总结求逆元的多种方法】

    1、当a和p互质时,逆元求解一般利用扩展欧几里得算法
    2、当p为质数的时候直接使用费马小定理,p为非质数使用欧拉函数
    3、当p为质数的时候,也可使用线性求[1,p-1]所有数逆元的方法

4.欧拉定理

先来谈谈【欧拉函数】

【版本一】

int phi(int n)
{
	int res=n;
	for(int i=2;i*i<=n;i++)
		if(n%i==0)
		{
		//n*(1-1/pi)=n-n/pi=(n*pi-n)/pi=n*(pi-1)/pi	
			res=res/i*(i-1);
			while(n%i==0)
				n/=i;
		}
	if(n>1)
		res=res/n*(n-1);
	return res;
}

【版本二】

void sieve(int n)
{
	phi[1]=1;
	for(int i=2;i<=n;i++)
	{
		if(!vis[i])
		{
			prime[++cnt]=i;
			phi[i]=i-1;
		}
		for(int j=1;j<=cnt&&i*prime[j]<=n;j++)
		{
			vis[i*prime[j]]=1;
			if(i%prime[j]==0)
			{
				phi[i*prime[j]]=phi[i]*prime[j];
				break;
			}
			else
				phi[i*prime[j]]=phi[i]*(prime[j]-1);
		}
	}
}

【欧拉定理】

不知道可以怎么用...降幂?

【补】指数循环节(似乎也可以叫“降幂公式”)

又一重点(黑板快敲烂了qwq...) 这个降幂公式蛮有用的

如其名字,就是用来降幂的,做题常用

5.莫比乌斯反演

不懂=。=,先咕掉


Part 3.数论、函数相关

1.置换(咕)

2.生成函数(咕)

3.组合数取模

找到几篇神仙博客:

https://www.cnblogs.com/fzl194/p/9095177.html

https://blog.csdn.net/u011815404/article/details/81433925

https://blog.csdn.net/lmhacm/article/details/75929159

自己常用的方法:

(1)组合公式+快速幂求逆元

要求:P是质数

可根据上述公式,用快速幂算出m!( n-m )!的逆元(或分开求),再用 n ! * inv [ m ! * ( n - m ) ! ](数据较大的话记得中途取模)

LL powMod(LL x, LL n, LL mod) 
{//快速幂求x^n%mod
    LL res=1;
    while(n) 
    {
        if(n&1)
            res=res*x%mod;
        x=x*x%mod;
        n>>=1;
    }
    return res;
}
LL inv(LL x,LL mod) 
{//求逆元
    return powMod(x,mod-2,mod);
}
LL fac[N];
int main() 
{
    LL n,m,mod;//要求mod是质数
    scanf("%lld%lld%lld",&n,&m,&mod);
    fac[0]=1;
    for(int i=1;i<=n;i++)//预处理求fac,fac[i]=i!%mod
        fac[i]=fac[i-1]*i%mod;
 
    //C(n,m) = n!*(m!%mod的逆元)*((n-m)!%mod的逆元)%mod
    LL res=fac[n]*inv(fac[m],mod)%mod*inv(fac[n-m],mod)%mod;
    printf("%lld\n",res);
    return 0;
}

此方法的例题我写了博客(nice):https://blog.csdn.net/qq_36294918/article/details/101561849

(2)杨辉三角打表(数据较小)

要求:n,m不超过10000

C_n^m=C_{n-1}^{m}+C_{n-1}^{m-1}

int Combination(int n)
{
     int i,j;
     a[0][0]=1;
     for(i=0;i<=n;i++)
     {
          a[i][0]=a[i][i]=1;
          for(j=1;j<i;j++)
          {
               a[i][j]=(a[i-1][j-1]+a[i-1][j])%mod;
          }
     }
     return 0;
}

(3)其他神仙方法有待学习... ...

4.卢卡斯定理(暂时咕,再看我脑壳就要bao了)

https://www.cnblogs.com/fzl194/p/9095177.html


Part 4.博弈相关

https://www.cnblogs.com/noobimp/p/10306311.html

自己以前做过一道博弈相关的小水题...看看这个找找感觉吧...

https://blog.csdn.net/qq_36294918/article/details/98368882

1.SG定理(咕...)

https://www.cnblogs.com/ECJTUACM-873284962/p/6921829.html

2.NIM(咕...)

https://www.cnblogs.com/easonliu/p/4472541.html


三、数据结构

1.线段树

 

2.树状数组

 


警告——前方大量飞鸽!


3.HLD(树链剖分)(咕)

4.LCT(跳舞链)(可能咕)

5.Treap(咕)

6.可并堆(咕)

7.树上差分(咕)

8.重构树(咕)

9.CDQ分治(可能咕)

10.莫队(可能咕)

11.分块(咕)


四、总结

啊,越复习感觉自己越菜...心里没啥把握...0.0...

加油加油再加油...

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值