CF 组合数学整理

懒得更新。

CF 267 E

我们只需要求出有序的方案数 ×n! 即可。

首先 n>m 时无解,又因为 nm105,所以 n105。因此,O(mn2) 复杂度可以接受。

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

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

dpi,j,k 代表考虑到第 i 位,有 j 个左端点了,k 个右端点了的方案数。答案是 dpm,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

cnti,j 代表 i 个东西,用 j 种颜色的方案数。则 cnti,j=cnti1,j1+cnti1,j×(j1)。复杂度 O(maxli2)

dpi,j 代表考虑到了第 i 层,这一层用了 j 个颜色的方案数。因为用的种类个数不同,集合也会不同,得出递推式 dpi,j=Amj×cntli,j×dpi1,kj!×cntli,j×dpi1,j。复杂度 O(L)

Amj 可以预处理。

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

分类讨论拐几次。

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

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

  • 两次:预处理 ui,j,di,j,li,j,ri,j 分别代表从哪个方向来能不能延申到 (i,j)。有两次转折,一定是两条横加上一个竖或者反之。对于一条横的情况:枚举是那一条横。从左到右枚举竖的地方。考虑先设 sum=ui,1+di,1。(初始)对于 j[2,m]ans+=sum×(ui,j+di,j)ans=ui,jui,j1+di,jdi,j1 因为不能两条竖线相邻,sum+=ui,j+di,j,若 si,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 中有多少包含它,的和,即可。设子序列为 a1,a2,,al

  • l=0a 为空,那么每一个 S 中的序列都包含,给答案贡献 mn

  • l>0,对于一个确定的序列,设 ai 出现的位置为 bi 考虑:第 bl 个位置往后,可以随便填,mnbl;从前 bi1 个位置选 l1 个放 a1,a2,,al1(bl1l1);为了避免重复,前 bl1 个位置不放 a 中的数的,每个数有 m1 个取法,(m1)jl。因此答案贡献 l=1ni=lnmni(m1)il(i1l1)

答案是 mn+l=1ni=lnmni(m1)il(i1l1)

=mn+i=1nmni+1j=0i1mj(m1)ij1(i1j)

=mn+i1nmni+1(2m1)j1,因为 (x+y)n=k=0n(nk)xnkyk

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 nm 的话,一定有一行是空的!

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

所以,答案就是:对于所有的行,其中一个空的行扔掉,其他的行,如果填完了,×1,否则有 cnt 个空位,其中 cnt1 个空位乱填,因此 ×2cnt1

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 @   SFlyer  阅读(58)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
点击右上角即可分享
微信分享提示