专题讲座5 组合数学 学习心得

贴个网址 :

ZJNU-2022暑期专题-组合数学 - Virtual Judge (vjudge.net)



学长贴的自闭知识

目录

知识点:

求逆元性价比最高公式

错排

Lucas

 例题:

E - Shinyruo and KFC

G - 硬币购物


知识点:

求逆元性价比最高公式

先求出尾巴的inv,然后层层往前回去。

rep(i,2,N-5){
	f[i]=f[i-1]*i%p;
}
inv[N-5]=fastpower(f[N-5],p-2,p);
nep(i,N-6,2){
	inv[i]=inv[i+1]*(i+1)%p;
}

错排

//错排递推公式
d[1]=0;d[2]=1;d[3]=2;
//d表示全错排n个数的情况有多少
rep(i,4,N){
	d[i]=((i-1)*(d[i-1]+d[i-2]))%p;
}
//故5个数错排3个,就是C(3,5)*d[3];

Lucas

//求组合,当q次询问余数p(<1e6且质数)的值会改变的时候使用。
int lucas(int n,int m){
	if (!m) return 1;
	return c(n%p,m%p)*lucas(n/p,m/p)%p;
}

 例题:

E - Shinyruo and KFC

大意:n个食物,每个食物ai个,m个队伍,每个队伍每种食物最多吃一个,求队伍分别为1~m的时候,分配食物吃完的不同情况。

学长说这是一道铜牌题。

这道题还有个题面,就是所有ai的和不超过1e5,而n也是1e5,那么不同的ai最多只有sqrt(1e5)个,所以抽屉原理,会有一堆重复的,这时候就可以unordered_map存进去,时间复杂度就变成了,n*sqrt(1e5)*快速幂logn,就能过了。

总结:铜牌题更注重对不同题目数据的处理。

#include <bits/stdc++.h>
#define rep(i,l,r) for (int i=l;i<=r;i++)
#define nep(i,r,l) for (int i=r;i>=l;i--)
#define pii pair<int,int>
#define int long long
#define CIO std::ios::sync_with_stdio(false)
using namespace std;
const int N=2e5+5;
const int p=998244353;
int a[N],t[N],tn[N];
int f[N],inv[N],finv[N];
unordered_map <int,int> mp;
int fastpower(int a,int x,int p){
	int ans=1;
	while (x){
		if (x&1) ans=(ans*a)%p;
		x=x/2;
		a=(a*a)%p;
	}
	return ans%p;
}

void Int(){
	f[0]=inv[0]=f[1]=inv[1]=finv[0]=finv[1]=1;
	rep(i,2,N-5){
		f[i]=f[i-1]*i%p;
	}
	inv[N-5]=fastpower(f[N-5],p-2,p);
	nep(i,N-6,2){
		inv[i]=inv[i+1]*(i+1)%p;
	}
}
int c(int m,int n){
	if (m>n) return 0;
	return f[n]*inv[n-m]%p*inv[m]%p;
}
void work(){
	Int();
	int n,m;cin>>n>>m;
	int ma=0;
	rep(i,1,n){
		cin>>a[i];
		mp[a[i]]++;
		ma=max(ma,a[i]);
	}
	int ans=0;
	unordered_map<int,int>::iterator it;
	int cnt=0;
	for(it=mp.begin();it!=mp.end();it++){
		t[++cnt]=it->second;
		tn[cnt]=it->first;
	}
	rep(i,1,m){
		if (i<ma) cout<<0<<endl;
		else{
			ans=1;
			rep(j,1,cnt){
				if (t[j]!=0){
					int zhi=fastpower(c(tn[j],i),t[j],p);
					if (zhi!=0){
						ans=ans*zhi%p;
					}
				}
			}
			cout<<ans<<endl;
		}
	}
}
signed main(){
	CIO;
	work();
	return 0;
}

G - 硬币购物

共有4种硬币。面值分别为c1​,c2​,c3​,c4​。

某人去商店买东西,去了n次,对于每次购买,他带了 di​ 枚 i种硬币,想购买 s 的价值的东西。请问每次有多少种付款方法。

这是一道dp+容斥的一道题,如果没有数量限制,就是一道完全背包的板子题目,我们假设一个超过限制,其他的就是完全背包,然后假设四遍,最后减掉,但发现会多减掉,这就是容斥原理了,然后再继续处理,暴力+-就好了,只有4种硬币。

#include <bits/stdc++.h>
#define rep(i,l,r) for (int i=l;i<=r;i++)
#define nep(i,r,l) for (int i=r;i>=l;i--)
#define pii pair<int,int>
#define int long long
#define CIO std::ios::sync_with_stdio(false)
using namespace std;
const int N=2e5+5;
const int p=998244353;
int c[10],dp[N];
int d[10],s;
int sum(int idx){
	return c[idx]*(d[idx]+1);
}
void work(){
	int n;
	rep(i,1,4){
		cin>>c[i];
	}
	dp[0]=1;
	rep(i,1,4){
		for (int j=c[i];j<N;j++){
			dp[j]+=dp[j-c[i]];
		}
	}
	cin>>n;
	rep(i,1,n){
		rep(j,1,4){
			cin>>d[j];
		}
		cin>>s;
		int ans=dp[s];
		if (s>=sum(1)) ans-=dp[s-sum(1)];
		if (s>=sum(2)) ans-=dp[s-sum(2)];
		if (s>=sum(3)) ans-=dp[s-sum(3)];
		if (s>=sum(4)) ans-=dp[s-sum(4)];
		if (s>=sum(1)+sum(2)) ans+=dp[s-sum(1)-sum(2)];
		if (s>=sum(1)+sum(3)) ans+=dp[s-sum(1)-sum(3)];
		if (s>=sum(1)+sum(4)) ans+=dp[s-sum(1)-sum(4)];
		if (s>=sum(2)+sum(3)) ans+=dp[s-sum(2)-sum(3)];
		if (s>=sum(2)+sum(4)) ans+=dp[s-sum(2)-sum(4)];
		if (s>=sum(3)+sum(4)) ans+=dp[s-sum(3)-sum(4)];
		if (s>=sum(1)+sum(2)+sum(3)) ans-=dp[s-sum(1)-sum(2)-sum(3)];
		if (s>=sum(1)+sum(3)+sum(4)) ans-=dp[s-sum(1)-sum(3)-sum(4)];
		if (s>=sum(1)+sum(2)+sum(4)) ans-=dp[s-sum(1)-sum(2)-sum(4)];
		if (s>=sum(2)+sum(3)+sum(4)) ans-=dp[s-sum(2)-sum(3)-sum(4)];
		if (s>=sum(1)+sum(2)+sum(3)+sum(4)) ans+=dp[s-sum(1)-sum(2)-sum(3)-sum(4)];
		cout<<ans<<endl;
	}
}
signed main(){
	CIO;
	work();
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

繁水682

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值