牛客练习赛64D 宝石装箱
题目大意
\(n\) 颗宝石装进 \(n\) 个箱子使得每个箱子中都有一颗宝石,第 \(i\) 颗宝石不能装入第 \(a_i\) 个箱子,求合法的装箱方案数对 \(\textbf{998244353}\) 取模。
两种装箱方案不同当且仅当两种方案中存在一颗编号相同的宝石装在不同编号的箱子中。\(1\le a_i\le n\le 8000\)。
题目分析
正难则反,直接求解不太容易,不妨用容斥去乱搞。
答案 \(=\) 总方案数 \(-\sum\limits_{i=1}^nf(x)\),\(f(i)\) 表示存在 \(i\) 个箱子不合法的方案数。
令 \(dp[i][j]\) 表示前 \(i\) 个箱子有 \(j\) 个放了不合法的宝石,其他 \(n-i\) 个箱子暂时不放宝石的方案数。
那么有 \(dp[i][j]=dp[i-1][j]+dp[i-1][j-1]\times t_i\),\(t_i\) 表示 \(i\) 在 \(a\) 中的出现次数。
那么前 \(n\) 个箱子至少有 \(i\) 个不合法的箱子的方案数为 \(dp[n][i]\times (n-i)!\)。
可是这样会重复计数。比如说 \(1\) 和 \(2\) 放在了不合法位置,后面 \(3\) 放在了不合法位置 \(x\) 和 \(1,3\) 放在了不合法位置,\(2\) 放在了 \(x\)。所以这个时候,我们就要再减去重复计数的方案数。
于是有:\(ans=n!-\sum\limits_{i=1}^n(-1)^i\times dp[n][i]\times (n-i)!=\sum\limits_{i=0}^n(-1)^i\times dp[n][i]\times (n-i)!\)。
意思是:至少有 \(0\) 个不合法的箱子的方案数 \(-\) 至少有 \(1\) 个不合法的箱子的方案数 \(+\) 至少有 \(2\) 个不合法的箱子的方案数 \(\cdots\)
时间复杂度 \(\mathcal{O(n^2)}\),常数比较小。
代码
#include <iostream>
#include <cstdio>
#include <climits>//need "INT_MAX","INT_MIN"
#include <cstring>//need "memset"
#include <numeric>
#include <algorithm>
#include <cmath>
#define int long long
#define enter putchar(10)
#define debug(c,que) std::cerr << #c << " = " << c << que
#define cek(c) puts(c)
#define blow(arr,st,ed,w) for(register int i = (st);i <= (ed); ++ i) std::cout << arr[i] << w;
#define speed_up() std::ios::sync_with_stdio(false),std::cin.tie(0),std::cout.tie(0)
#define mst(a,k) memset(a,k,sizeof(a))
#define stop return(0)
const int mod = 998244353;
inline int MOD(int x) {
if(x < 0) x += mod;
return x % mod;
}
namespace Newstd {
char buf[1 << 21],*p1 = buf,*p2 = buf;
inline int getc() {
return p1 == p2 && (p2 = (p1 = buf) + fread(buf,1,1 << 21,stdin),p1 == p2) ? EOF : *p1 ++;
}
inline int read() {
int ret = 0,f = 0;char ch = getc();
while (!isdigit(ch)) {
if(ch == '-') f = 1;
ch = getc();
}
while (isdigit(ch)) {
ret = (ret << 3) + (ret << 1) + ch - 48;
ch = getc();
}
return f ? -ret : ret;
}
inline double double_read() {
long long ret = 0,w = 1,aft = 0,dot = 0,num = 0;
char ch = getchar();
while (!isdigit(ch)) {
if (ch == '-') w = -1;
ch = getchar();
}
while (isdigit(ch) || ch == '.') {
if (ch == '.') {
dot = 1;
} else if (dot == 0) {
ret = (ret << 3) + (ret << 1) + ch - 48;
} else {
aft = (aft << 3) + (aft << 1) + ch - '0';
num ++;
}
ch = getchar();
}
return (pow(0.1,num) * aft + ret) * w;
}
inline void write(int x) {
if(x < 0) {
putchar('-');
x = -x;
}
if(x > 9) write(x / 10);
putchar(x % 10 + '0');
}
}
using namespace Newstd;
const int N = 8005;
int a[N],fact[N],dp[N];
//dp[i][j]:前 i 个箱子有 j 个放了不合法的箱子,其他 n - i 个箱子暂时先不放宝石的方案数
//第一维压掉
int n;
#undef int
int main(void) {
#define int long long
n = read();
for (register int i = 1;i <= n; ++ i) a[read()] ++;
dp[0] = 1;
for (register int i = 1;i <= n; ++ i) {
for (register int j = i;j >= 1; -- j) {
(dp[j] += dp[j - 1] * a[i]) %= mod;
}
}
fact[0] = 1;
for (register int i = 1;i <= n; ++ i) (fact[i] = fact[i - 1] * i) %= mod;
int ans = 0;
for (register int i = 0;i <= n; ++ i) {
if (i % 2 == 1) ans = MOD(ans + MOD(-dp[i] * fact[n - i]));
else (ans += dp[i] * fact[n - i]) %= mod;
}
printf("%lld\n",ans);
return 0;
}