数数 题解
write by 小超手123
题意:
现在有四种物品,分别有 \(n_{1},n_{2},n_{3},n_{4}\) 个,有多少种排列物品的方案使得任意两个相邻物品的种类不同。
\(n_{1},n_{2} \le 200, \ \ n_{3},n_{4} \le 50000\)。
分析:
可以考虑先把物品 \(A,B\) 排列好,再把物品 \(C,D\) 插入进去。
需要注意的是 \(ABB\) 中 \(BB\) 中间必须填入 \(C,D\)。
记 \(f_{i,j,k,0/1}\) 表示用了 \(i\) 个物品 \(A\),\(j\) 个物品 \(B\),其中相邻的且相等有 \(k\) 对,当前最后一个选的物品 \(A/B\)。
step 1 : 分配哪些空位要填入 \(C,D\)
因此有 \(n_{1}+n_{2}+1\) 个空位,总共需用 \(C,D\) 填入 \(j\) 个空位,有 \(i\) 个空位已经强制要求必须填。\(i,j\) 均需要枚举。
那么方案数为:
step 2 : 把 \(C,D\) 填入空位
可以发现中间空位填的 \(C,D\) 只有三种情况:
- 类型一:形如 \(CDCDC\)。相当于先放一个 \(C\) 进去,然后放若干个 \([DC]\)。即 \(C\) 的个数比 \(D\) 的个数多 \(1\)。记这样的有 \(X\) 种。
- 类型二:形如 \(DCDCD\)。相当于先放一个 \(D\) 进去,然后放若干个 \([CD]\)。即 \(D\) 的个数比 \(C\) 的个数多 \(1\)。记这样的有 \(Y\) 种。
- 类型三:形如 \(DCDC\) 或 \(CDCD\)。相当于直接放若干个 \([CD]\) 或 \([DC]\)。即 \(D\) 的个数与 \(C\) 的个数相等。记这样的有 \(Z\) 种。
不难发现 \(n_{3}-n_{4}=X-Y,Z=j-X-Y\)。因此只需要再枚举 \(X\) 即可。
所以可以给每个要填空位分配一种类型:\(\binom{j}{X} \times \binom{j-X}{Y}\)。
即上文所述 \([CD],[DC]\)(\(C,D\) 一定成对出现) 的总个数为 \(num\)。显然 \(num = \frac{n_{3}+n_{4}-X-Y}{2}\)。
将这 \(num\) 个\([CD],[DC]\) 放进 \(j\) 个空位里。需要注意的是对于类型三的空位,至少要放一个 \([CD]\) 或 \([DC]\)。
把 \(num\) 减掉 \(Z\),然后插板即可。因此有 \(\binom{num-Z+j-1}{j-1}\) 种情况。
由于类型三有两种小情况,因此还要乘上 \(2^{Z}\)。
乘法原理,需要枚举 \(i,j,X\)。
最后答案就是:
时间复杂度 \(O((n_{1}+n_{2})^3)\)。
代码:
#include<bits/stdc++.h>
#define int long long
#define N 100000
#define mod 1000000007
using namespace std;
int Pow(int a, int n) {
if(n == 0) return 1;
if(n == 1) return a;
int x = Pow(a, n / 2);
if(n % 2 == 0) return x * x % mod;
else return x * x % mod * a % mod;
}
int inv(int x) {
return Pow(x, mod - 2);
}
int Inv[N + 10], fac[N + 10], h[N + 10];
void init() {
Inv[0] = fac[0] = h[0] = 1;
for(int i = 1; i <= N; i++) {
fac[i] = fac[i - 1] * i % mod;
h[i] = h[i - 1] * 2 % mod;
}
Inv[N] = inv(fac[N]);
for(int i = N - 1; i >= 1; i--) Inv[i] = Inv[i + 1] * (i + 1) % mod;
}
int C(int n, int m) {
if(n < m || n < 0 || m < 0) return 0;
return fac[n] * Inv[m] % mod * Inv[n - m] % mod;
}
int f[210][210][410][2];
int n1, n2, n3, n4, ans;
void upd(int &x, int y) {
x = (x + y) % mod;
}
void DP() {
f[1][0][0][0] = f[0][1][0][1] = 1;
for(int i = 0; i <= n1; i++)
for(int j = 0; j <= n2; j++)
for(int k = 0; k <= n1 + n2; k++) {
upd(f[i + 1][j][k + 1][0], f[i][j][k][0]);
upd(f[i][j + 1][k][1], f[i][j][k][0]);
upd(f[i][j + 1][k + 1][1], f[i][j][k][1]);
upd(f[i + 1][j][k][0], f[i][j][k][1]);
}
}
void Sol() {
for(int j = 0; j <= n1 + n2 + 1; j++)
for(int i = 0; i <= min(j, n1 + n2 - 1); i++)
for(int X = 0; X <= j; X++) {
int Y = X - (n3 - n4);
if(Y < 0) continue;
int Z = j - X - Y, num = (n3 + n4 - X - Y) / 2;
upd(ans, (f[n1][n2][i][0] + f[n1][n2][i][1]) * C(n1 + n2 + 1 - i, j - i) % mod
* C(j, X) % mod * C(j - X, Y) % mod * C(num - Z + j - 1, j - 1) % mod * h[Z] % mod);
}
}
signed main() {
init();
cin >> n1 >> n2 >> n3 >> n4;
DP();
Sol();
cout << ans;
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!