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!!!