10.21练习题目总结

信封问题

题意:

某人写了 n 封信和 n 个信封,如果所有的信都装错了信封。求所有信都装错信封共有多少种不同情况。

题解:

这不是错排么直接秒,太简单就不放代码了

\[D(n)=(n-1)(D(n-1)+D(n-2)) \]

[JSOI2011] 分特产

题目描述

JYY 带队参加了若干场 \(\text{ACM/ICPC}\) 比赛,带回了许多土特产,要分给实验室的同学们。

JYY 想知道,把这些特产分给 \(n\) 个同学,一共有多少种不同的分法?当然,JYY 不希望任何一个同学因为没有拿到特产而感到失落,所以每个同学都必须至少分得一个特产。

例如,JYY 带来了 \(2\) 袋麻花和 \(1\) 袋包子,分给 \(A\) 和 \(B\) 两位同学,那么共有 \(4\) 种不同的
分配方法:

\(A\):麻花, \(B\):麻花、包子

\(A\):麻花、麻花, \(B\):包子

\(A\):包子, \(B\):麻花、麻花

\(A\):麻花、包子, \(B\):麻花

题解

设\(f(i)\)表示至少有\(i\)​个同学没有被分到特产的方案数,

设\(g(i)\)表示恰好\(i\)个同学没有被分到特产的方案数,

显然的是\(ans=g(0)\)

\(f(i)\)是好求的先选出\(i\)个同学不分特产,枚举\(m\)个特产,每个特产的贡献为把\(a[j]\)分给\(n-i\)个人可有人不分的方案数,这用插板法是好做的,即为\(C_{a[j]+n-i-1}^{n-i-1}\)

小小总结一下:

\[f[i]=C_n^i\prod_{i=1}^mC_{a[j]+n-i-1}^{n-i-1} \]

再用二项式反演可直接求\(g[0]\)

\[g[i]=\sum_{j=i}^n(-1)^jf[j] \]

代码:

#include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define se second
#define fi first
using namespace std;
const int N=5e3+10;
const int mod=1e9+7;
const ll inf=0x3f3f3f3f3f3f3f3f;
ll maxx=N-10;
ll n,m,k,ans,cnt,rt;
ll a[N],b[N],f[N];
ll jie[N],ni[N];
ll ppow(ll n,ll m){ll s=1;while(m){if(m&1)s=s*n%mod;n=n*n%mod;m/=2;}return s;}
ll c(int n,int m){
	if(n<m||n<0||m<0)
		return 0;
	return jie[n]*ni[m]%mod*ni[n-m]%mod;
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	jie[0]=1;
	for(int i=1;i<=maxx;i++)jie[i]=(jie[i-1]*i)%mod;
	ni[maxx]=ppow(jie[maxx],mod-2);
	for(int i=maxx;i;i--)ni[i-1]=(ni[i]*i)%mod;
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		cin>>a[i];
	}
	for(int i=0;i<=n;i++){
		f[i]=c(n,i);
		for(int j=1;j<=m;j++){
			f[i]=(f[i]*c(a[j]+n-i-1,n-i-1))%mod;
		}
	}
	for(int i=0;i<=n;i++){
		ans=(ans+ppow(-1,i)*f[i]%mod+mod)%mod;
	}
	cout<<ans;
}

P1450 [HAOI2008] 硬币购物

题目描述

共有 \(4\) 种硬币。面值分别为 \(c_1,c_2,c_3,c_4\)。

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

题解

第一眼看到觉得是多重背包但一看时间范围T炸了

可以先算完全背包的方案这是好求的,枚举硬币枚举钱数即可

再考虑容斥:无限制的-有一个限制的+有两个限制的-有三个限制的+有四个限制的

4维状压每次如果当前状态中的\(i\)号硬币有限制则应选个数+1个\(i\)号硬币才符合限制就差不多了

代码:

#include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define se second
#define fi first
using namespace std;
const int N=1e5+10;
const int mod=998244353;
const ll inf=0x3f3f3f3f3f3f3f3f;
const ll INF=0xefefefefefefefef;
ll n,m,k,lz,c,ans;
ll a[N],b[N],dp[N];
ll read(){ll x;cin>>x;return x;}
ll ppow(ll n,ll m){ll s=1;while(m){if(m&1)s=s*n%mod;n=n*n%mod;m/=2;}return s;}
vector<ll>g[N];
int main(){
	for(int i=1;i<=4;i++)cin>>a[i];
	dp[0]=1;
	for(int i=1;i<=4;i++)
	for(int j=a[i];j<=N;j++)dp[j]+=dp[j-a[i]]; 
	for(int T=read();T;T--){
		for(int i=1;i<=4;i++)cin>>b[i];
		ll sum=read();
		ans=0;
		for(int i=0;i<15;i++){
			ll t=sum;
			int cnt=0;
			for(int j=1;j<=4;j++){
				if(i&(1<<(j-1))){
					cnt++;
					t-=a[j]*(b[j]+1);
				}
			}
			if(t<0)continue;
			if(cnt&1){
				ans-=dp[t];
			}else{
				ans+=dp[t];
			}
		}
		cout<<ans<<'\n';
	}
}

水仙花

题目描述

我们定义一个整数序列 \(a_1,a_2,⋯ ,a_n\) 是青蛙的,当且仅当:

  • 对所有 \(1≤i≤n\),都有 \(1≤a_i≤m\)。

  • 对所有

    \(1≤i<n\),有以下两个条件至少成立一个:

    • \(a_i≤a_{i+1}\)
    • \((a_i mod a_{i+1})>0\)

我想要知道,对于给定的 \(n\)与 \(m\),有多少种青蛙的序列,因此你要帮我计算出答案。由于这个数字可能很大, 因此你只需要输出它对 998244353取模后的值即可。

题解

由于是在容斥作业中,所以考虑容斥

有\(n\)个\(m\)以内的数要想超出限制\(a_i mod a_{i+1}=0\)转换一下就是\(a_i\)为\(a_{i+1}\)的整倍数若想全部都不满足条件\(n\)最长为\(log_2(m)\),预处理\(f[i][j]\)表示长度为\(i\)以\(j\)开头的方案数,\(F[i]\)表示长度为i的全不合法数,显然\(F[i]=\sum_{j=1}^mf[i][j]\)

然后就是基本的容斥,\(dp[i]\)表示的是长度为\(i\)的合法方案数\(dp[i]=\sum_{j=1}^{min(i,30)}(-1)^{j-1}dp[i-j]*F[j]\)

代码:

#include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define se second
#define fi first
using namespace std;
const int N=1e5+10;
const int mod=998244353;
const ll inf=0x3f3f3f3f3f3f3f3f;
ll maxx=N-10;
ll n,m,k,ans,cnt,rt;
ll a[N],dp[N],f[30][N];
ll jie[N],ni[N];
ll ppow(ll n,ll m){ll s=1;while(m){if(m&1)s=s*n%mod;n=n*n%mod;m/=2;}return s;}
ll c(int n,int m){
	if(n<m||n<0||m<0)
		return 0;
	return jie[n]*ni[m]%mod*ni[n-m]%mod;
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	jie[0]=1;
	for(int i=1;i<=maxx;i++)jie[i]=(jie[i-1]*i)%mod;
	ni[maxx]=ppow(jie[maxx],mod-2);
	for(int i=maxx;i;i--)ni[i-1]=(ni[i]*i)%mod;
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		f[1][i]=1;
	}
	for(int i=1;i<=30;i++){
		for(int j=1;j<=m;j++){
			for(int k=j*2;k<=m;k+=j){
				(f[i+1][k]+=f[i][j])%=mod;
			}
			(a[i]+=f[i][j])%=mod;
		}
	}
	dp[0]=1;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=min(30,i);j++){
			dp[i]=(dp[i]+((j&1)?1:-1)*dp[i-j]*a[j]%mod+mod)%mod;
		}
	}
	cout<<dp[n];
}

P10596 BZOJ2839 集合计数

题目描述

一个有 \(N\) 个元素的集合有 \(2^N\) 个不同子集(包含空集),现在要在这 \(2^N\) 个集合中取出若干集合(至少一个),使得它们的交集的元素个数为 \(K\),求取法的方案数,答案模 \(10^9+7\)。

题解

一个有 \(N\) 个元素的集合有 \(2^N\) 个不同子集,有\(2^{2^{N}}\)种选法,由于不能为空就有\(2^{2^{N}}-1\)种方案

设\(f[i]\)表示至少有\(i\)个元素在总交集里,\(g[i]\)表示恰好有\(i\)个元素在交集里,\(ans=g[k]\)

\[f[i]=C_n^i(2^{2^{n-i}}-1) \]

选\(i\)个放在集合里剩下的\(n-i\)个数随便选但不能一个都不选

二项式反演即可推\(g[k]\)

\[g[k]=\sum_{i=k}^n(-1)^{i-k}C_i^kf[i] \]

直接把\(f[i]\)带进去就行不用预处理

\[g[k]=\sum_{i=k}^n(-1)^{i-k}C_i^kC_n^i(2^{2^{n-i}}-1) \]

\(2^{2^{n-i}}-1\)要用欧拉定理不然会炸掉(感谢Qinluyan帮查错)
代码:

#include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define se second
#define fi first
using namespace std;
const int N=1e6+10;
const int mod=1000000007;
const ll inf=0x3f3f3f3f3f3f3f3f;
ll maxx=N-10;
ll n,m,k,ans,cnt,rt;
ll a[N],b[N],f[N];
ll jie[N],ni[N];
ll qpow(ll n,ll m){ll s=1;while(m){if(m&1)s=s*n%(mod-1);n=n*n%(mod-1);m/=2;}return s;}
ll ppow(ll n,ll m){ll s=1;while(m){if(m&1)s=s*n%mod;n=n*n%mod;m/=2;}return s;}
ll c(int n,int m){
	if(n<m||n<0||m<0)
		return 0;
	return jie[n]*ni[m]%mod*ni[n-m]%mod;
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	jie[0]=1;
	for(int i=1;i<=maxx;i++)jie[i]=(jie[i-1]*i)%mod;
	ni[maxx]=ppow(jie[maxx],mod-2);
	for(int i=maxx;i;i--)ni[i-1]=(ni[i]*i)%mod;
	cin>>n>>k;
	for(int i=k;i<=n;i++){
		ll now=ppow(-1,i-k)*c(i,k)%mod*c(n,i)%mod*((ppow(2,qpow(2,n-i))-1+mod)%mod)%mod;
		ans=(ans+now+mod)%mod;
	}
	cout<<ans;
}

luoguP4859 已经没有什么好害怕的了

题目描述

已经使 Madoka 有签订契约,和自己一起战斗的想法后,Mami 忽然感到自己不再是孤单一人了呢。

于是,之前的谨慎的战斗作风也消失了,在对 Charlotte 的傀儡使用终曲——Tiro Finale 后,Mami 面临着即将被 Charlotte 的本体吃掉的局面。

这时,已经多次面对过 Charlotte 的 Homura 告诉了学 OI 的你这样一个性质:Charlotte 的结界中有两种具有能量的元素,一种是“糖果”,另一种是“药片”,各有 \(n\) 个。在 Charlotte 发动进攻前,“糖果”和“药片”会两两配对,若恰好“糖果”比“药片”能量大的组数比“药片”比“糖果”能量大的组数多 \(k\) 组,则在这种局面下,Charlotte 的攻击会丟失,从而 Mami 仍有消灭 Charlotte 的可能。

你必须根据 Homura 告诉你的“糖果”和“药片”的能量的信息迅速告诉 Homura 这种情况的个数.

题解

注意到\(a,b\)中的数一定互不相等所以一定不是糖>药就是药>糖设前一个为A,后一个为B

\(A+B=n\)且\(A-B>k\)则\(A\)至少要\((n+k)/2\)个令\(k={k+n\over2}\)问题转化为需要k个A的方案数

对\(a,b\)从小到大排序,设\(r[i]\)表示\(b[j]<a[i]\)的最大\(j\),由于排序了所以\(r[i]\)一定单调不增,可以\(O(n)\)求出\(r[i]\)

设\(dp[i][j]\)表示当前到\(i\)位置匹配了\(j\)个\(A\)的方案数,显然可以从\(dp[i-1][j]\)转移过来即当前位置的\(a[i]\)没有新产生\(A\).也可以由\(dp[i-1][j-1]\)转移\(a[i]\)最多和\(r[i]个b[j]\)匹配成\(A\)所以可以有\(r[i]-j+1\)种(前\(j-1\)个\(A\)已占用了\(j-1\)个\(r[i]\)的长度),dp就求完了

继续往下走,要求\(f\)和\(g\)进行二项式反演

设\(f[i]\)表示至少\(i\)个\(A\),\(g[i]\)表示恰好有\(i\)个\(A\)

\(f[i]\)实际上是好求的 就是\(f[i]=dp[n][i]*(n-i)!\),后面\((n-i)!\)就是除了\(i\)组\(A\)其余瞎放即可

\(g[k]\)就可用\(f[i]\)来得到:\(g[k]=\sum_{i=k}^{n}(-1)^{i-k}C_i^kf[i]\)

代码:

#include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define se second
#define fi first
using namespace std;
const int N=3e3+10;
const int mod=1e9+9;
const ll inf=0x3f3f3f3f3f3f3f3f;
ll maxx=N-10;
ll n,m,k,ans,cnt,rt;
ll a[N],b[N],dp[N][N],f[N],r[N];
ll jie[N],ni[N];
ll ppow(ll n,ll m){ll s=1;while(m){if(m&1)s=s*n%mod;n=n*n%mod;m/=2;}return s;}
ll c(int n,int m){
	if(n<m||n<0||m<0)
		return 0;
	return jie[n]*ni[m]%mod*ni[n-m]%mod;
}
int main(){
   // freopen("sale.in","r",stdin);
   // freopen("sale.out","w",stdout);
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	jie[0]=1;
	for(int i=1;i<=maxx;i++)jie[i]=(jie[i-1]*i)%mod;
	ni[maxx]=ppow(jie[maxx],mod-2);
	for(int i=maxx;i;i--)ni[i-1]=(ni[i]*i)%mod;
	cin>>n>>k;
	k=(k+n)/2;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	for(int i=1;i<=n;i++){
		cin>>b[i];
	}
	sort(a+1,a+n+1);
	sort(b+1,b+n+1);
	ll pos=0;
	for(int i=1;i<=n;i++){
		while(pos<n&&b[pos+1]<a[i])pos++;
		r[i]=pos;
	}
	dp[0][0]=1;
	for(int i=1;i<=n;i++){
		dp[i][0]=dp[i-1][0];
		for(int j=1;j<=i;j++){
			dp[i][j]=(dp[i-1][j]+1ll*max(0ll,r[i]-j+1)*dp[i-1][j-1]%mod)%mod;
		}
	}
	for(int i=0;i<=n;i++){
		f[i]=(dp[n][i]*jie[n-i])%mod;
	}
	ans=0;
	for(int i=k;i<=n;i++){
		if((i-k)&1){
			ans=(ans-1ll*c(i,k)*f[i]%mod)%mod;
		}else{
			ans=(ans+1ll*c(i,k)*f[i]%mod)%mod;
		}
	}
	cout<<(ans+mod)%mod;
}

P5376 [THUPC 2019] 过河卒二

题目描述

Kiana 喜欢玩象棋,尤其是喜欢用象棋玩过河卒的游戏。在传统的过河卒问题中,Kiana 需要控制一个卒从起点走到终点,在路中避开一个对方的马的攻击,然后假装不会算并询问你从起点到终点的路径总数。

在今天的过河卒二游戏中,Kiana 还是控制一个卒在一个 \(N\times M\) 的棋盘上移动,初始时卒位于左下方坐标为 \((1,1)\) 位置,但为了增加难度,Kiana 对游戏规则做出了一些修改。传统的过河卒每步只能向上或向右移动 \(1\) 格,Kiana 规定自己的过河卒二还可以在一步中向右上方移动 \(1\) 格,即如果当前卒位于坐标 \((x,y)\) 处,则下一步可以走到 \((x+1,y)\) 、\((x,y+1)\) 或 \((x+1,y+1)\) 中的任意一格里面去,同时 Kiana 认为,如果两种移动方案在某一步时卒移动的方向(右、上或右上)不同,则两种方案就是不同的,例如从 \((1,1)\) 先走到 \((1,2)\) 再走到 \((2,2)\) 、从 \((1,1)\) 先走到 \((2,1)\) 再走到 \((2,2)\) 和从 \((1,1)\) 直接走到 \((2,2)\) 是三种不同的移动方案。

其次,过河卒二的终点不再是一个特定的位置,Kiana 规定卒可以从棋盘的上方或右方走出棋盘,此时就视为游戏成功。注意在走出棋盘时仍然有方向选择的不同,例如若过河卒位于 \((1,M)\) 处,则下一步它可以向右或者向右上用两种方式走出棋盘,若过河卒位于 \((N,M)\) 处,则下一步它可以向上、向右或者向右上用三种方式走出棋盘,以不同的方式走出棋盘仍然被算作是不同的移动方案。

此外,对方马的攻击范围不再是有规律的几个位置,而是 Kiana 规定好的 \(K\) 个特定坐标,并要求过河卒在移动的过程中不能走到这 \(K\) 个坐标的任何一个上,在除这些坐标以外的位置上过河卒都可以按规则自由移动。

现在 Kiana 想知道,过河卒二有多少种不同的移动方案可以走出棋盘,这个答案可能非常大,她只想知道方案数对 \(59393\) 取模后的结果。由于她不会算,所以希望由你来告诉她。

题解

这个题让我想到了AT_dp_y Grid 2这个题,没做过的可以先做这个题,对本题的解法基本相同,同样的\(K\)同样的走格子,不同的是可以斜着走

首先看到\(k≤20\)容易想到k为突破口,这个走出网格实现起来不是很好做,可以将走出网格的方案转换为到\((n+1,m+1)\)的位置,这样跟那个题就更像了,然后由那个题的启发,可以将障碍转换为点,并将\((n+1,m+1)\)放到障碍集合,记\(f[i]\)表示按\(x_i\)为第一关键字\(y_i\)为第二关键字排序后,走到第\(i\)个障碍且不经过任意一个障碍的方案数,里这样算走到障碍的方案时到\((n+1,m+1)\)且不碰到任何总方案数就有了。

再计算时可以将所有坐标向左下偏移一格这样就是\((0,0)-(n,m)\)的方案数了!

由于排过序了所以在算\(i\)号障碍时,从\(1\)到\((i-1)\)的障碍一定已经算过了,直接转移即可;

设\(w(x,y)\)表示走到从\((0,0)\)走到\((x,y)\)的方案数

\(dp[i]=w(x[i],y[i])-\sum_{j=1}^kdp[j]*w(x[i]-x[j],y[i]-y[j])(x[i]≥x[j]且y[i]≥y[j])\)

现在就是求\(w\)了,设走了\(i\)步斜着的就有\(x-y-i\)步可被分配,先分\(i\)步斜着的,再分\(x-i\)步向右的,再分\(y-i\)步向上的:

\[w(x,y)=C_{x-y-i}^{i,x-i,y-i}=\sum_i{(x+y-i)!\over i!(x−i)!(y−i)!} \]

但由于出题人的私人范围\(1≤N≤10^9\),预处理阶乘会爆炸,但由于模数只有\(59393\)所以可以用Lucas定理来处理阶乘

代码:

#include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define se second
#define fi first
using namespace std;
const int N=1e5+10;
const int mod=59393;
const ll inf=0x3f3f3f3f3f3f3f3f;
ll maxx=mod-1;
ll n,m,k,ans,cnt,rt;
ll b[N],dp[N],f[N],r[N];
ll jie[N],ni[N];
ll read(){ll x;cin>>x;return x;}
ll ppow(ll n,ll m){ll s=1;while(m){if(m&1)s=s*n%mod;n=n*n%mod;m/=2;}return s;}
ll c(int n,int m){
	if(n<m||n<0||m<0)
		return 0;
	return jie[n]*ni[m]%mod*ni[n-m]%mod;
}
struct zzz{
	ll x,y;
	friend bool operator <(zzz x1,zzz x2){
		return x1.x==x2.x?x1.y<x2.y:x1.x<x2.x;
	}
}a[N];
ll w(ll n,ll m){
	ll s=0;
	for(int i=0;i<=min(n,m);i++){
		if((n+m-i)%mod<i%mod+(n-i)%mod||(n+m-i)/mod<i/mod+(n-i)/mod)continue;
		ll now=jie[(n+m-i)%mod]*ni[i%mod]%mod*ni[(n-i)%mod]%mod*ni[(m-i)%mod]%mod;
		now=now*jie[(n+m-i)/mod]%mod*ni[i/mod]%mod*ni[(n-i)/mod]%mod*ni[(m-i)/mod]%mod;
		s=(now+s)%mod;
	}
	return s;
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	jie[0]=1;
	for(int i=1;i<=maxx;i++)jie[i]=(jie[i-1]*i)%mod;
	ni[maxx]=ppow(jie[maxx],mod-2);
	for(int i=maxx;i;i--)ni[i-1]=(ni[i]*i)%mod;
	cin>>n>>m>>k;
	for(int i=0;i<k;i++){
		a[i].x=read()-1;
		a[i].y=read()-1;
	}
	sort(a,a+k);
	a[k]={n,m};
	for(int i=0;i<=k;i++){
		dp[i]=w(a[i].x,a[i].y);
		for(int j=0;j<i;j++){
			if(a[j].x<=a[i].x&&a[j].y<=a[i].y){
				dp[i]=(dp[i]-dp[j]*w(a[i].x-a[j].x,a[i].y-a[j].y)%mod+mod)%mod;
			}
		}
	}
	cout<<dp[k];
}
posted @ 2026-10-21 17:09  Build-xhz31  阅读(8)  评论(2)    收藏  举报