nowcoder-204323 宝石装箱 (容斥原理,dp)
题目链接:nowcoder-204323 宝石装箱
题意
\(n\) 颗宝石装进 \(n\) 个箱子使得每个箱子中都有一颗宝石,第 \(i\) 颗宝石不能装入第 \(a_i\) 个箱子,求合法的装箱方案数对 \(998244353\) 取模。两种装箱方案不同当且仅当两种方案中存在一颗编号相同的宝石装在不同编号的箱子中。( \(1\leq a_i \leq n \leq 8000\) )
思路
这道题正向思考不容易求解,利用反向思维:一共有 \(n!\) 个排列,把不合法的方案数减去,就是合法的方案数。
不合法的方案数可以这样求:
记 \(cnt[i]\) 表示有 \(cnt[i]\) 个宝石不能装入第 \(i\) 个箱子。这 \(cnt[i]\) 个宝石选 \(1\) 个放在第 \(i\) 个箱子有 \(cnt[i]\) 种方案,剩下的 \(n-1\) 个宝石有 \((n-1)!\) 种排列,那么第 \(i\) 个箱子放的宝石不合法的方案数就是 \((n-1)!\cdot cnt[i]\) 种。
选定 \(1\) 个箱子装的宝石不合法的方案数共有:\(f_1 = (n-1)!\cdot\sum_{i=1}^n cnt[i]\) ;
选定 \(2\) 个箱子装的宝石不合法的方案数共有:\(f_2=(n-2)!\cdot \sum_{i=1}^n \sum_{j=i+1}^ncnt[i]\cdot cnt[j]\) ;
选定 \(3\) 个箱子装的宝石不合法的方案数共有:\(f_3=(n-3)!\cdot\sum_{i=1}^n\sum_{j=i+1}^n\sum_{k=j+1}^ncnt[i]\cdot cnt[j]\cdot cnt[k]\) ;
......
里面的和式可以通过二维 dp 预处理,令 \(dp[i][j]\) 表示前 \(i\) 个 \(cnt\) ,取 \(j\) 个的乘积之和,即 \(f_i=(n-i)!\cdot dp[n][i]\) ,则:\(dp[i][j]=dp[i-1][j]+dp[i-1][j-1]\cdot cnt[i]\) 。
记至少有 \(1\) 个箱子装的宝石不合法的方案数为 \(f\) ,\(f_1\) 是否就是 \(f\) ?我们选定第 \(i\) 个箱子放的宝石不合法时,其他宝石是随意排列的,也就包含了其他宝石放置不合法的情况;再选定其他箱子放的宝石不合法时,也会包含第 \(i\) 个箱子宝石不合法的情况,这就重复计算了。所以 \(f_1\ne f\),利用容斥原理,有 \(f=\sum_{i=1}^n(-1)^{i-1}f_i\) ,最终答案为 \(n!-f\) 。
代码实现
#include <cstdio>
typedef long long LL;
const int maxn = 8010, mod = 998244353;
LL dp[2][maxn], f[maxn];
int cnt[maxn];
int main() {
int n;
scanf("%d", &n);
f[0] = 1;
for (int i = 1, x; i <= n; i++) {
scanf("%d", &x);
cnt[x]++;
f[i] = f[i-1] * i % mod;
}
dp[0][0] = 1;
for (int i = 1; i <= n; i++) {
dp[i&1][0] = 1;
for (int j = 1; j <= i; j++) {
dp[i&1][j] = (dp[(i-1)&1][j] + dp[(i-1)&1][j-1] * cnt[i]) % mod;
}
}
LL ans = f[n];
for (int i = 1; i <= n; i++) {
if (i & 1) ans = (ans - f[n-i] * dp[n&1][i] % mod + mod) % mod;
else ans = (ans + f[n-i] * dp[n&1][i]) % mod;
}
printf("%lld\n", ans);
return 0;
}