CF 组合数学整理

懒得更新。

CF 267 E

我们只需要求出有序的方案数 \(\times n!\) 即可。

首先 \(n>m\) 时无解,又因为 \(nm\le 10^5\),所以 \(n\le \sqrt{10^5}\)。因此,\(\mathcal{O}(mn^2)\) 复杂度可以接受。

一个位置不能同时放多个左端点或多个右端点,因此,只有四种情况:不放,放一个左端点,放一个右端点,放一个左端点一个右端点。

注意到,如果你放完了左端点和右端点,对应唯一一个方案(不能包含)。

\(dp_{i,j,k}\) 代表考虑到第 \(i\) 位,有 \(j\) 个左端点了,\(k\) 个右端点了的方案数。答案是 \(dp_{m,n,n}\)。注意第 \(x\) 位,只能放一个左端点或放一个左端点一个右端点。

Code
#include <bits/stdc++.h>

using namespace std;

#define de(x) cout<<#x<<"="<<x<<endl

using ll = long long;

const int N = 330;
const int mod = 1e9+7;

int n,m,x;
ll fac[N],dp[2][N][N];

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);

	cin>>n>>m>>x;
	if (n>m){
		cout<<0<<endl;
		return 0;
	}
	fac[0]=1;
	for (int i=1; i<N; i++){
		fac[i]=fac[i-1]*i%mod;
	}
	dp[0][0][0]=1;
	for (int i=1; i<=m; i++){
		int cur=i&1;
		int pre=cur^1;
		for (int j=0; j<=n; j++){
			for (int k=0; k<=j; k++){
				dp[cur][j][k]=0;
				if (j && k){
					(dp[cur][j][k]+=dp[pre][j-1][k-1])%=mod;
				}
				if (j && j-1>=k){
					(dp[cur][j][k]+=dp[pre][j-1][k])%=mod;
				}
				if (i!=x){
					if (k){
						(dp[cur][j][k]+=dp[pre][j][k-1])%=mod;
					}
					(dp[cur][j][k]+=dp[pre][j][k])%=mod;
				}
		//		cout<<i<<","<<j<<","<<k<<":"<<dp[cur][j][k]<<endl;
			}
		}
	}
//	cout<<fac[n]<<endl;
	cout<<dp[m&1][n][n]*fac[n]%mod<<endl;
	return 0;
}

// don't waste time!!!

CF 140 E

\(cnt_{i,j}\) 代表 \(i\) 个东西,用 \(j\) 种颜色的方案数。则 \(cnt_{i,j}=cnt_{i-1,j-1}+cnt_{i-1,j}\times(j-1)\)。复杂度 \(\mathcal{O}(\max l_i^2)\)

\(dp_{i,j}\) 代表考虑到了第 \(i\) 层,这一层用了 \(j\) 个颜色的方案数。因为用的种类个数不同,集合也会不同,得出递推式 \(dp_{i,j}=A_{m}^{j}\times cnt_{l_i,j}\times \sum dp_{i-1,k}-j!\times cnt_{l_i,j}\times dp_{i-1,j}\)。复杂度 \(\mathcal{O}(L)\)

\(A_{m}^{j}\) 可以预处理。

Code
#include <bits/stdc++.h>

using namespace std;

#define de(x) cout<<#x<<"="<<x<<endl

using ll = long long;

const int N = 5e3+3;
const int M = 1e6+6;

ll n,m,mod;
ll l[M],fac[N],A[N];
ll cnt[N][N],dp[2][N];

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
 
	cin>>n>>m>>mod;
	for (int i=1; i<=n; i++){
		cin>>l[i];
	}
	fac[0]=1;
	for (int i=1; i<N; i++){
		fac[i]=fac[i-1]*i%mod;
	}
	A[0]=1;
	for (int i=1; i<N; i++){
		A[i]=A[i-1]*(m-i+1)%mod;
	}
	cnt[0][0]=1;
	for (int i=1; i<N; i++){
		for (int j=1; j<=min(i*1ll,m); j++){
			cnt[i][j]=(cnt[i-1][j-1]+cnt[i-1][j]*(j-1)%mod)%mod;
		}
	}
	ll sum=1;
	for (int i=1; i<=n; i++){
		int cur=i&1;
		for (int j=1; j<=l[i]; j++){
			dp[cur][j]=A[j]*cnt[l[i]][j]%mod*sum%mod;
			(dp[cur][j]+=mod-fac[j]*cnt[l[i]][j]%mod*dp[cur^1][j]%mod)%=mod;
		}
		sum=0;
		for (int j=1; j<=l[i]; j++){
			(sum+=dp[cur][j])%=mod;
		}
		memset(dp[cur^1],0,sizeof dp[0]);
	}
	cout<<sum<<endl;
	return 0;
}

// don't waste time!!!

CF 518 F

分类讨论拐几次。

  • 零次:直接枚举行或列。

  • 一次:枚举“转折点”。

  • 两次:预处理 \(u_{i,j},d_{i,j},l_{i,j},r_{i,j}\) 分别代表从哪个方向来能不能延申到 \((i,j)\)。有两次转折,一定是两条横加上一个竖或者反之。对于一条横的情况:枚举是那一条横。从左到右枚举竖的地方。考虑先设 \(sum=u_{i,1}+d_{i,1}\)。(初始)对于 \(j\in[2,m]\)\(ans+=sum\times (u_{i,j}+d_{i,j})\)\(ans-=u_{i,j}\cdot u_{i,j-1}+d_{i,j}\cdot d_{i,j-1}\) 因为不能两条竖线相邻,\(sum+=u_{i,j}+d_{i,j}\),若 \(s_{i,j}=*\)\(sum=0\)。其实,\(sum\) 就是当前的前面可以与它匹配的方案数。另一种情况同理。

三种答案加起来即可。

Code
#include <bits/stdc++.h>

using namespace std;

#define de(x) cout<<#x<<"="<<x<<endl

using ll = long long;

const int N = 2e3+3;

ll n,m,u[N][N],d[N][N],l[N][N],r[N][N],ans;
char s[N][N];

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);

	cin>>n>>m;
	for (int i=0; i<n; i++){
		for (int j=0; j<m; j++){
			cin>>s[i][j];
			u[i][j]=d[i][j]=l[i][j]=r[i][j]=(s[i][j]=='.');
			s[i][j]=(s[i][j]=='.');
		}
	}
	for (int i=1; i<n; i++){
		for (int j=0; j<m; j++){
			u[i][j]&=u[i-1][j];
		}
	}
	for (int i=0; i<n; i++){
		for (int j=1; j<m; j++){
			l[i][j]&=l[i][j-1];
		}
	}
	for (int i=n-2; i>=0; i--){
		for (int j=m-1; j>=0; j--){
			d[i][j]&=d[i+1][j];
		}
	}
	for (int i=n-1; i>=0; i--){
		for (int j=m-2; j>=0; j--){
			r[i][j]&=r[i][j+1];
		}
	}
	for (int i=1; i<n-1; i++){
		int fl=1;
		for (int j=0; j<m; j++){
			fl&=s[i][j];
		}
		ans+=fl;
	}
	for (int i=1; i<m-1; i++){
		int fl=1;
		for (int j=0; j<n; j++){
			fl&=s[j][i];
		}
		ans+=fl;
	}
	for (int i=1; i<n-1; i++){
		for (int j=1; j<m-1; j++){
			ans+=(u[i][j]+d[i][j])*(l[i][j]+r[i][j]);
		}
	}
	for (int i=1; i<n-1; i++){
		int sum=u[i][1]+d[i][1];
		for (int j=2; j<m-1; j++){
			ans+=sum*(u[i][j]+d[i][j])-u[i][j]*u[i][j-1]-d[i][j]*d[i][j-1];
			sum+=u[i][j]+d[i][j];
			if (!s[i][j]){
				sum=0;
			}
		}
	}
	for (int j=1; j<m-1; j++){
		int sum=l[1][j]+r[1][j];
		for (int i=2; i<n-1; i++){
			ans+=sum*(l[i][j]+r[i][j])-l[i][j]*l[i-1][j]-r[i][j]*r[i-1][j];
			sum+=l[i][j]+r[i][j];
			if (!s[i][j]){
				sum=0;
			}
		}
	}
	cout<<ans<<endl;
	return 0;
}

// don't waste time!!!

CF 660 E

求出每一个子序列在 \(S\) 中有多少包含它,的和,即可。设子序列为 \(a_1,a_2,\cdots,a_l\)

  • \(l=0\)\(a\) 为空,那么每一个 \(S\) 中的序列都包含,给答案贡献 \(m^n\)

  • \(l>0\),对于一个确定的序列,设 \(a_i\) 出现的位置为 \(b_i\) 考虑:第 \(b_l\) 个位置往后,可以随便填,\(m^{n-b_l}\);从前 \(b_i-1\) 个位置选 \(l-1\) 个放 \(a_1,a_2,\cdots,a_{l-1}\)\(\binom{b_l-1}{l-1}\);为了避免重复,前 \(b_l-1\) 个位置不放 \(a\) 中的数的,每个数有 \(m-1\) 个取法,\((m-1)^{j-l}\)。因此答案贡献 \(\displaystyle \sum_{l=1}^{n}\sum_{i=l}^{n}m_{n-i}(m-1)^{i-l}\binom{i-1}{l-1}\)

答案是 \(m^n+\displaystyle \sum_{l=1}^{n}\sum_{i=l}^{n}m_{n-i}(m-1)^{i-l}\binom{i-1}{l-1}\)

\(\displaystyle=m^n+\sum_{i=1}^{n}m^{n-i+1}\sum_{j=0}^{i-1}m^j (m-1)^{i-j-1}\binom{i-1}{j}\)

\(\displaystyle=m^n+\sum_{i-1}^{n}m^{n-i+1}(2m-1)^{j-1}\),因为 \(\displaystyle (x+y)^n=\sum_{k=0}^{n}\binom{n}{k}x^{n-k}y^k\)

Code
#include <bits/stdc++.h>

using namespace std;

#define de(x) cout<<#x<<"="<<x<<endl

using ll = long long;

const int N = 1e6+6;
const ll mod = 1e9+7;

ll n,m,ans,x[N],y[N];

void init(){
	x[0]=1;
	y[0]=1;
	for (int i=1; i<=n; i++){
		x[i]=x[i-1]*m%mod;
		y[i]=y[i-1]*(2*m-1)%mod;
	}
}

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);

	cin>>n>>m;
	init();
	for (int i=1; i<=n; i++){
		(ans+=x[n-i+1]*y[i-1]%mod)%=mod;
	}
	(ans+=x[n])%=mod;
	cout<<ans<<endl;
	return 0;
}

// don't waste time!!!

CF 40 E

\(k<\min(n,m)\) 可以告诉我们什么呢?如果 \(n<m\),就 swap \(n\)\(m\) 的话,一定有一行是空的!

考虑无解的情况。一个解中所有数的积不仅是 \((-1)^n\) 也是 \((-1)^m\),所以,若 \(n\)\(m\) 奇偶性不同,无解。同样,如果行填完了(除了空的行),空的行唯一的填法。如果一个填完的行积不是 \(-1\),亦无解。

所以,答案就是:对于所有的行,其中一个空的行扔掉,其他的行,如果填完了,\(\times 1\),否则有 \(cnt\) 个空位,其中 \(cnt-1\) 个空位乱填,因此 \(\times 2^{cnt-1}\)

Code
#include <bits/stdc++.h>

using namespace std;

#define de(x) cout<<#x<<"="<<x<<endl

using ll = long long;

const int N = 1e3+3;

int n,m,k,mod;
int cnt[N],it[N];

ll pw(ll x,ll y){
	if (y<0){
		return 1;
	}
	ll res=1;
	while (y){
		if (y&1){
			res=res*x%mod;
		}
		x=x*x%mod;
		y>>=1;
	}
	return res;
} 

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);

	cin>>n>>m>>k;
	bool f=0;
	if (n<m){
		f=1;
		swap(n,m);
	}
	for (int i=1; i<=n; i++){
		cnt[i]=m;
		it[i]=1;
	}
	for (int i=0; i<k; i++){
		int a,b,c;
		cin>>a>>b>>c;
		if (f){
			swap(a,b);
		}
		cnt[a]--;
		it[a]*=c;
	}
	if (n%2!=m%2){
		cout<<0<<endl;
		return 0;
	}
	cin>>mod;
	f=0;
	ll res=1;
	for (int i=1; i<=n; i++){
		if (cnt[i]==0 && it[i]==1){
			cout<<0<<endl;
			return 0;
		}
		if (cnt[i]==m && !f){
			f=1;
			continue;
		}
		else{
			res=res*pw(2,cnt[i]-1)%mod;
		}
	}
	cout<<res<<endl;
	return 0;
}

// don't waste time!!!
posted @ 2023-10-30 14:24  SFlyer  阅读(34)  评论(0编辑  收藏  举报