动态规划指南

同余定理

同余性质

同余性质是指在任意情况下,都有:

  • $n\times m\bmod p$=$(n\bmod p)\times(m\bmod p)\bmod p$
  • $n+m\bmod p$=$(n\bmod p)+(m\bmod p)\bmod p$
  • $n-m\bmod p$=$(n\bmod p)-(m\bmod p)\bmod p$

除法不满足同余性质。

欧拉定理

欧拉函数:$\varphi(n)$ 表示在 $1$ 和 $n$ 之间与 $n$ 互质的个数,即 $\sum_{i=1}^{n}[\gcd(i,n)=1]$。

欧拉定理:如果 $\gcd(n,p)=1$,则有 $n^p\equiv1\pmod p$。

乘法逆元

乘法逆元指如果 $a\times b\equiv1\pmod p$,则称 $b$ 为 $a$ 的 $p$ 意义下逆元。

快速幂求逆元

因为 $a\times b\equiv1\pmod p$,则有 $a\times b\equiv a^{p-1}\pmod p$,同时除以 $a$,则有 $b\equiv a^{p-2}\pmod p$,所以可以用快速幂求逆元。

递推求逆元

令 $div=\lfloor\frac{i}{p}\rfloor$,$mod=i\mod p$,则有:
$$div\times i+mod\equiv 0\pmod p$$
同时转为逆元,则:
$$div^{-1}\times i{-1}+mod\equiv 0\pmod p$$
$$i^{-1}\equiv div^{-1}\times mod^{-1}\pmod p$$
$$i^{-1}\equiv div=\lfloor\frac{i}{p}\rfloor^{-1}\times(i\bmod p)^{-1}\pmod p$$
就可以递推求了。

ll inv[MAXN];
inline void prework(ll mod){
	inv[1]=1;//初始值
	for(int i=2;i<MAXN;++i){
		inv[i]=(mod-mod/i)*inv[mod%i]%mod;//递推式 
	} 
}

gcd 定理

exgcd

$$a\times x_1+b\times y_1=\gcd(a,b)$$
$$b\times x_2+(a\bmod b)\times y_2=\gcd(a,a\bmod b)$$
之后不断往下递归就可以了。

ll exgcd(ll a,ll b,ll &x,ll &y){
	if(!b){
		x=1;
		y=0;//肯定有这么一组 
		return a;
	}
	ll d=exgcd(b,a%b,x,y);//公式 
	x=y;
	y=d-(a/b)*y;//公式 
	return d;
}

例题

直接套板子即可。

#include<bits/stdc++.h>
#define getchar getchar_unlocked
#define MOD 19260817
using namespace std;
typedef long long ll;
inline ll scan(void){
	ll res=0,f=1;
	char c=getchar();
	while(!isdigit(c)){
		if(c=='-'){
			f=-1;
		}
		c=getchar();
	}
	while(isdigit(c)){
		(res=(res<<1)+(res<<3)+(c^48))%=MOD;
		c=getchar();
	}
	return f*res;
}
inline void exgcd(ll a,ll b,ll &x,ll &y){
	if(b==0){
		x=1;
		y=0;
		return;
	}
	exgcd(b,a%b,x,y);
	ll tmp=x;
	x=y;
	y=tmp-a/b*y;
}
int main(){
	ll a=scan(),b=scan(),x=0,y=0;
	exgcd(b,MOD,x,y);
	x=(x%MOD+MOD)%MOD;
	printf("%lld",x*a%MOD);
	return 0;
}

裴蜀定理

裴蜀定理指如果要满足 $ax+by=c$,则需要满足 $\gcd(a,b)\mid c$。没什么特别好讲的,主要主语结论。

例题

套板子即可。

#include<bits/stdc++.h>
using namespace std;
int main(){
	int n,ans;
	scanf("%d %d",&n,&ans);
	while(--n){
		int x;
		scanf("%d",&x);
		if(x<0){
			x=-x;
		}
		if(x!=0){
			ans=__gcd(ans,x);
		}
	}
	printf("%d",ans);
	return 0;
}

积性函数和筛法

欧拉函数

欧拉函数上面讲到过,其实可以拆解成:$\Pi_{p\in Prime,p\mid n}(p-1)$,所以,可以用欧拉筛筛出来。

莫比乌斯函数

莫比乌斯函数的定义是这样的:
$$\mu(n)=\begin{cases}
1&n\in Prime\
0&p^2\mid n,p\in Prime\
(-1)k&n=\Pi_{i=1}p_i^{c_i},p_i\in Prime
\end{cases}$$
这也是积性函数,所以可以用欧拉筛筛出来。

数论分块

数论分块是一种优化方法,当式子变成形如:
$$\Large\sum_{i=1}^{n}G(i)\times H(\lfloor\frac{n}{i}\rfloor)$$
这样的式子,数论分块能够将其优化至将近 $\sqrt{n}$ 的复杂度。

我们可以发现,$\lfloor\frac{n}{i}\rfloor$ 这一段会有很多相同的情况,于是,我们可以利用 $\lfloor\frac{n}{i}\rfloor$ 的相同进行优化。

设 $l$ 和 $r$ 为这一块的左端点和右端点,那么有 $\lfloor\frac{n}{l}\rfloor=\lfloor\frac{n}{r}\rfloor$,可以得出 $r$ 最大为 $\lfloor\frac{n}{\lfloor\frac{n}{l}\rfloor}\rfloor$,那么就可以这样进行跳块了。

for(int l=1,r=0;l<=n;l=r+1){//跳块 
	r=n/(n/l);
	res+=(S(r)-S(l-1))*G(n/l);//前缀和加上分块的形式 
}

莫比乌斯反演

有一个性质,$\sum_{d\mid n}\mu(d)=[n=1]$,所以,这可以将形如:
$$F(n)=\sum_{d\mid n}f(\lfloor\frac{n}{d}\rfloor)$$
$$f(n)=\sum_{d\mid n}\mu(d)\times F(\lfloor\frac{n}{d}\rfloor)$$
还可以将形如:
$$[\gcd(a_1,a_2,a_3\dots a_n)=1]$$
变成:
$$\sum_{d\mid a_1,d\mid a_2,d\mid a_3\dots d\mid a_n}\mu(d)$$

例题

基于上面的反演公式,可以转化为:
$$\sum_{d=1}{n}\sum_{t=1}\rfloor}\mu(t)\sum_{i=1}^{\lfloor\frac{n}{d\times t}\rfloor}\varphi(i)\sum_{j=1}^{\lfloor\frac{m}{d\times t}\rfloor}\varphi(j)$$
之后掏出一个 $G(n)=\sum_{i=1}^{n}\varphi(i)$,有:
$$\sum_{d=1}{n}\sum_{t=1}\rfloor}\mu(t)\times G(\lfloor\frac{n}{d\times t}\rfloor)\times G(\lfloor\frac{m}{d\times t}\rfloor)$$
再掏出一个 $H(n,m,t)=\sum_{i=1}^{t}\mu(t)\times G(\lfloor\frac{n}{i}\rfloor)\times G(\lfloor\frac{m}{i}\rfloor)$。
$$\sum_{d=1}^{n}H(\lfloor\frac{n}{d}\rfloor,\lfloor\frac{m}{d}\rfloor,\lfloor\frac{t}{d}\rfloor)$$
之后预处理加上数论分块优化就可以了。

#include<bits/stdc++.h>
#define MAXN 50005
#define MAXM 55
#define MOD 23333
using namespace std;
int t,n,m;
int phi[MAXN],mob[MAXN];
vector<int> prim,G[MAXN],S[MAXM][MAXM];
bool flag[MAXN];
inline void prework(){
	flag[1]=phi[1]=mob[1]=1;
	for(int i=2;i<MAXN;++i){
		if(!flag[i]){
			prim.push_back(i);
			phi[i]=i-1;
			mob[i]=-1;
		}
		for(int j=0;j<prim.size()&&i*prim[j]<MAXN;++j){
			flag[i*prim[j]]=true;
			if(i%prim[j]){
				phi[i*prim[j]]=phi[i]*(prim[j]-1)%MOD;
				mob[i*prim[j]]=-mob[i];
			}else{
				phi[i*prim[j]]=phi[i]*prim[j]%MOD;
				break;
			}
		}
	}
	for(int i=1;i<MAXN;++i){
		G[i].push_back(0);
		for(int j=1;i*j<MAXN;++j){
			G[i].push_back((G[i].back()+phi[i*j])%MOD);
		}
	}
	for(int i=1;i<MAXM;++i){
		for(int j=1;j<MAXM;++j){
			S[i][j].resize(MAXN/max(i,j)+1);
			for(int d=1;d*max(i,j)<MAXN;++d){
				for(int k=1;k*d*max(i,j)<MAXN;++k){
					(S[i][j][k*d]+=G[d][i]*G[d][j]%MOD*mob[d]%MOD)%=MOD;
				}
			}
			for(int k=1;k*max(i,j)<MAXN;++k){
				(S[i][j][k]+=S[i][j][k-1])%=MOD;
			}
		}	
	}
}
inline int blocked(){
	int ans=0,lim=m/MAXM;
	for(int i=1;i<=lim;++i){
		for(int j=1;i*j<=lim;++j){
			(ans+=1ll*mob[i]*G[i][n/i/j]%MOD*G[i][m/i/j]%MOD)%=MOD;
		}	
	}
	for(int l=lim+1,r;l<=n;l=r+1){
		r=min(n/(n/l),m/(m/l));
		(ans+=((S[n/l][m/l][r]-S[n/l][m/l][l-1])%MOD+MOD)%MOD)%=MOD;
	}
	return ans;
}
int main(){
	prework();
	int t;
	scanf("%d",&t);
	while(t--){
		scanf("%d %d",&n,&m);
		if(n>m){
			swap(n,m);
		}
		printf("%d\n",blocked());
	} 
	return 0;
}

杜教筛

杜教筛是一种优化方法。它能够在 $\operatorname{O}(n^{\frac{2}{3}})$ 的时间复杂度内求出积性函数的前缀和,在算法竞赛中不常用。原理是将函数拆成一系列函数的迪丽雷克卷积,然后用数论分块优化。

迪丽雷克卷积

迪丽雷克卷积用 $$ 号表示,假如有数论函数 $f1$ 和 $f2$,则 $f=f1f2$ 有 $f(n)=\sum_{d\mid n}f1(d)\times f2(\frac{n}{d})$。

证明

要求积性函数 $f$ 的前缀和,定义前缀和函数 $S(n)=\sum_{i=1}^{n}f(i)$。再随便找一个积性函数 $g$,定义卷积 $h=f*g$。
$$\sum_{i=1}^{n}h(i)$$
$$\sum_{i=1}^{n}\sum_{d\mid i}f(d)\times g(\frac{i}{d})$$
$$\sum_{i=1}{n}g(i)\times\sum_{d=1}\rfloor}f(i)$$
$$\sum_{i=1}^{n}g(i)\times S(\lfloor\frac{n}{i}\rfloor)$$
接下来,考虑 $g(1)=1$ 之后求。
$$\sum_{i=1}^{n}g(i)\times S(\lfloor\frac{n}{i}\rfloor)-\sum_{i=2}^{n}g(i)\times S(\lfloor\frac{n}{i}\rfloor)$$
$$g(1)=\sum_{i=1}{n}h(i)-\sum_{i=2}g(i)\times S(\lfloor\frac{n}{i}\rfloor)$$
之后,可以用数论分块加递归。时间复杂度 $\operatorname{O}(n^{\frac{3}{4}})$。但是,可以用线性筛先求出 $n^{\frac{2}{3}}$ 的函数前缀和,然后再使用杜教筛,这样复杂度会最小化。可以用记忆花搜索来避免不必要的搜索。

组合计数

卡特兰数

卡特兰数是指从 $n$ 个元素中选 $m$ 个数的方案,具体可以用证明法。

  • 设 $A_{n}^{m}$ 为从 $n$ 个里面选取 $m$ 个数,包含顺序不同的情况,为 $\frac{!n}{!(n-m)}$
  • 那么不包含顺序的 $C_{n}^{m}$ 为 $\frac{A_nm}{A_m1}$,是 $\frac{!n}{!(n-m)\times !m}$。
  • 下面两项再取模的时候可以乘以逆元。

容斥原理

容斥是指去除重复的情况。例如要求 $|S1\cap S2|$,则可以用 $|S1|+|S2|-|S1\cup S2|$。这个就是容斥原理。

例题

这一道题目考虑正难则反。首先令 $ans=\sum c_i\times d_i$,减去不合法的情况。

首先,我们把它当成一个完全背包,处理出来一个 dp 数组存储方案数,转移方程为 $dp_i\to dp_i+dp_{i-v_i}$。

考虑减去不合法的情况为更多的硬币,所以就是 $ans\to ans-dp_{V-d_i-1}\times c_i$。

#include<bits/stdc++.h>
#define MAXN 100001
using namespace std;
typedef long long ll;
ll dp[MAXN];
int main(){
	int c[5],test;
	scanf("%lld %lld %lld %lld %lld",&c[1],&c[2],&c[3],&c[4],&test);
	dp[0]=1;
	for(int i=1;i<=4;++i){
		for(int j=c[i];j<MAXN;++j){
			dp[j]+=dp[j-c[i]];
		}
	}
	while(test--){
		int d[5],sum,ans=0;
		scanf("%lld %lld %lld %lld %lld",&d[1],&d[2],&d[3],&d[4],&sum);
		for(int i=0;i<=15;++i){
			int cnt=sum,val=1;
			for(int j=1;j<=4;++j){
				if((i>>(j-1))&1){
					cnt-=c[j]*(d[j]+1);
					val*=-1;
				}
			}
			if(cnt<0){
				continue;
			}
			ans+=val*dp[cnt];
		}
		printf("%lld\n",ans);
	}
	return 0;
}
posted @ 2024-09-19 15:25  hisy_qwq  阅读(0)  评论(0编辑  收藏  举报