排列组合、crt、费马小定理,容斥原理,lucas定理,组合数取模

一些公式&定理

lucas定理

CnmCn/pm/p×Cn%pm%p(modp)

代码


int lucas(int n,int m,int p){
	if(m>n)return 0;
	if(m==0)return 1;
	if(n<p&&m<p)return 1ll*jc[n]*inv[m]%p*inv[n-m]%p;
	return lucas(n/p,m/p,p)*lucas(n%p,m%p,p)%p;
}

错排

f[n]=(n1)(f[n1]+f[n2])

考虑加入一个元素n,通过一步操作得到错排

  • n1个数是错排,n随便交换一个,方案数(n1)f[n1]

  • n1个数有一个在原位置,共n1种可能,剩下的数构成错排f[n2]n只能和它交换,方案数(n1)f[n2]

  • n1个数有两个或以上个数在原位置,一步无法得到错排

加起来即可

crt 中国剩余定理

求解

{xa1(modn1)xa2(modn2)xak(modnk)

其中ni两两互质

  • 计算P,为所有ni的乘积

  • 计算ci=P/ni,以及ci1(modni)

  • 答案x=i=1kaicici1(modP)

证明,x%nicj0aicici11所以得到了需要的答案

严谨过程

image

code
#include<cstdio>
#include<cstring>

using namespace std;

typedef long long ll;

ll a[15],p[15],m[15],inv[15];

ll slow(ll x,ll y,ll mod){
	ll ans=0;
	for(;y;y>>=1,x=(x+x)%mod)if(y&1)ans=(ans+x)%mod;
	return ans;
}

ll exgcd(ll a,ll b,ll &x,ll &y){
	if(b==0){
		x=1;y=0;
		return a;
	}
	ll gcd=exgcd(b,a%b,y,x);
	y-=a/b*x;
	return gcd;
}

ll INV(ll a,ll b){
	ll x,y;
	ll gcd=exgcd(a,b,x,y);
	return (x%b+b)%b;
}

int main()
{
	int n;scanf("%d",&n);
	for(int i=1;i<=n;++i)scanf("%lld %lld",&p[i],&a[i]);
	ll P=1;for(int i=1;i<=n;++i)P*=p[i];
	for(int i=1;i<=n;++i)m[i]=P/p[i];
	for(int i=1;i<=n;++i)inv[i]=INV(m[i],p[i]);
	ll x=0;
	for(int i=1;i<=n;++i)x=(x+slow(slow(a[i],m[i],P),inv[i],P))%P;
	printf("%lld\n",x);
	return 0;
}

excrt

使用扩展欧几里得算法两两合并

{xa1(modn1)xa2(modn2)

x=a1+pn1=a2qn2

pn1+qn2=a2a1(modlcm)

code
#include<cstdio>
#include<cstring>

using namespace std;

typedef long long ll;

ll res[100005],p[100005];


ll exgcd(ll a,ll b,ll &x,ll &y){
	if(b==0){
		x=1;y=0;
		return a;
	}
	ll gcd=exgcd(b,a%b,y,x);
	y-=a/b*x;
	return gcd;
}


ll china(int n){
	ll a,b,c,x,y,gcd,ans=res[1];
	a=p[1];
	for(int i=2;i<=n;++i){
		b=p[i];c=res[i]-ans;
		int gcd=exgcd(a,b,x,y);
		if(c%gcd!=0)return -1;
		x=(x*(c/gcd)%(b/gcd)+(b/gcd))%(b/gcd);
		ans=x*a+ans;
		a=a/gcd*b;
	}
	return ans;
}

int main()
{
	int n;
	while(scanf("%d",&n)!=EOF){
		for(int i=1;i<=n;++i)scanf("%lld %lld",&p[i],&res[i]);
		if(n==1){printf("%lld\n",res[1]);continue;}
		printf("%lld\n",china(n));
	}
	return 0;
}

exlucas

跟lucas没啥关系。。

根据唯一分解定定理

p=q1α1q2α2qrαr

求解

{a1(nm)(modq1α1)a2(nm)(modq2α2)ar(nm)(modqrαr)

就可以使用crt合并

现在考虑计算

ai=(nm)modqiαi

无法直接计算,如果移除因子q最后统一计算呢

n!qxm!qy(nm)!qzqxyzmodqk

现在考虑求

n!qxmodqk

image

显然现在就是求这东西

反过来,展开有

n!=qnq(nq)!(i,(i,q)=1qki)nqk(i,(i,q)=1nmodqki)

后面那两个奇怪的东西就是(n!)p,只不过分成了两部分

为什么可以这样分?

因为aa+qk(modqk)

qnq这东西不用求

(nq)!递归求解

剩下那个暴力就行

code
#include<cstdio>
#include<cstring>

using namespace std;

typedef long long ll;

ll w[105];

ll qpow(ll x,ll y,ll mod){
	ll ans=1;
	for(;y;y>>=1,x=1ll*x*x%mod)if(y&1)ans=1ll*ans*x%mod;
	return ans;
}

ll exgcd(ll a,ll b,ll &x,ll &y){
	if(b==0){
		x=1;y=0;
		return a;
	}
	ll gcd=exgcd(b,a%b,y,x);
	y-=a/b*x;
	return gcd;
}

ll inv(ll a,ll p){
	ll x,y;
	ll gcd=exgcd(a,p,x,y);
	return (x%p+p)%p;
}
ll get_f(ll n,ll p,ll pk){
	if(!n)return 1;
	ll res=1,ls=n%pk;
	for(ll i=2;i<=ls;++i)if(i%p)res=1ll*res*i%pk;
	ll num=res;
	for(ll i=ls+1;i<pk;++i)if(i%p)res=1ll*res*i%pk;
	return 1ll*get_f(n/p,p,pk)*qpow(res,n/pk,pk)%pk*num%pk;
}


ll get_c(ll n,ll m,ll p,ll pk){
	ll res=0,d=n-m;
	for(ll i=p;i<=n;i*=p)res+=n/i;
	for(ll i=p;i<=m;i*=p)res-=m/i;
	for(ll i=p;i<=d;i*=p)res-=d/i;
	return 1ll*qpow(p,res,pk)*get_f(n,p,pk)%pk*inv(get_f(m,p,pk),pk)%pk*inv(get_f(d,p,pk),pk)%pk;
}
ll exlucas(ll n,ll m,ll p){
	if(m==n)return 1;
	ll lp=p;ll ans=0;
	for(ll i=2;i*i<=lp;++i){
		if(lp%i)continue;
		ll pk=1;while(lp%i==0){lp/=i,pk*=i;}
		ll c=p/pk;
		ans=(ans+1ll*get_c(n,m,i,pk)*c%p*inv(c,pk)%p)%p;
	}
	if(lp!=1){
		ll c=p/lp;
		ans=(ans+1ll*get_c(n,m,lp,lp)*c%p*inv(c,lp)%p)%p;
	}
	return ans;
}

int main(){
	ll mod,n,m;
	scanf("%lld%lld%lld",&mod,&n,&m);
	for(ll i=1;i<=m;++i)scanf("%lld",&w[i]);
	ll sum=0;for(ll i=1;i<=m;++i)sum+=w[i];
	if(sum>n)printf("Impossible\n");
	else{
		ll ans=exlucas(n,sum,mod);
		for(ll i=1;i<=m;++i){
			ans=1ll*ans*exlucas(sum,w[i],mod)%mod;
			sum-=w[i];
		}
		printf("%lld\n",ans%mod);
	}
	return 0;
}

题目乱写

A. 排队

两种情况

  • 老师中间是男生

男生排列Ann

老师插空An+12

女生插空An+3m

  • 老师中间是女生

男生排列Ann

老师+一个女生整体法Cn+11A22

剩下女生插空An+2m1

打个高精

B. combination

lucas模板,关于lucas的证明我没看明白,先不管了

C. 排列计数

基本是错排板子,加一个简单组合数就行了

D. Perm 排列计数

把大小关系画出来,发现是个二叉堆,然后组合数+树DP

E. 集合计数

容斥

f[i]表示交集至少i个的方案数

首先钦定这i个交集cni

剩下的nk个数能构成2nk个集合,每个集合都可以选或不选,去掉空集22nk1

然后容斥一下就行了

F. 牡牛和牝牛

简单DP,有排列组合做法,但没必要

G BZOJ 4403序列统计

首先l,r是假的,只有rl有用

然后枚举长度,隔板法处理

H DZY Loves Math II

题意转化RL个不同元素,取1N个元素,有多少种取法,直接组合数就行
x=
然后问题就是一个背包了...吗?

发现值域太大,完全跑不出来

但是可以发现

n=kS+a

注意a不一定小于S

分成两部分求解

kS,每个S可以由S/pipi构成,每个pi都有可能,用隔板法就是ck+cnt1cnt1

a这部分直接背包就行,但是注意由于上面的计算,这里需要保证每个pi使用次数小于S/pi

多重背包?其实不用,完全背包再加一个dp[i]=dp[is]就行了

I. 虔诚的墓主人

这题我觉得是扫描线,但是严重怀疑不是正解,然后,,,就是扫描线,加上一个简单的组合数就行

J. 地精部落

妙极了,不会做的蓝题系列

首先看性质

  1. 在一个数列中,若ii+1不相邻,直接交换这两个数字就可以组成一个新的数列
  2. 把波动数列中的每个数字ai变成(n+1)ai会得到互补的数列,即且新数列的山峰与山谷情况相反
  3. 数列有对称性

状态设计

dp[i][j]表示选择1i这些数字,第一位是j并且是山峰的方案数

转移

因为性质1dp[i][j]+=dp[i][j1](dp[i][j1]中,因为j1是峰,j一定不与j1相邻)

如果jj1相邻,那么把j放到以j1为开头并且j1是谷的数列前,因为性质2,那么有dp[i][j]+=dp[i1][i+1j]

答案

因为性质3,答案就是i=1ndp[n][i]2

K. 看电影

这题应该放到高精题单里!

假设存在一个k+1号座位,然后把座位k+1与座位1连上,这样每个人都有座位,合法的方案里第k+1个座位应该是没有人的,枚举哪个位置是k+1断开这个环即可

合法方案数(k+1)n1×(kn+1)

总方案数kn

然后就是高精了

L. 曹冲养猪

crt板子

M. Strange Way to Express Integers

excrt板子

N. 礼物

式子好推就是Cnsum×Csuma1×Csuma1a2....

然后就是exlucas板子

O. 古代猪文

欧拉定理+lucas+crt

P. Per

可重集的排列+康拓展开+逆元,感觉有点exlucas的思想

posted @   Chen_jr  阅读(118)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示