牛客练习赛64 D【容斥+背包】

牛客练习赛64 D.宝石装箱

Description

(n)颗宝石装进(n)个箱子使得每个箱子中都有一颗宝石。第(i)颗宝石不能装入第(a_i)个箱子。求合法的装箱方案对(998244353)取模。
两种装箱方案不同当且仅当两种方案中存在一颗编号相同的宝石装在不同编号的箱子中。

(nle 8000)

题解:
直接计算肯定不好做,考虑用容斥来做
(g_x)(n)个宝石中(x)个宝石放在不合法的位置的方案数
那么我们的答案可以用容斥表示为(sum_{i=0}^{n}(-1)^icdot g_icdot (n-i)!)
其中(g_icdot (n-i)!)可以表示为至少选择(i)个宝石放在不合法的位置的方案数,其中必然会出现重复计数,比如固定了(1,3)不合法然后在后面随意放置的情况下(2)放在了不合法位置,和固定了(1,2)不合法然后在后面随意放置的情况下(3)放在了不合法位置,所以这里要利用容斥去解决
现在考虑如何计算(g_x)
可以令(g[i][j])表示:只考虑前(i)个箱子的情况下,有(j)个宝石放在不合法的位置的方案数,(A[i])表示有多少个宝石放在第(i)个箱子是不合法的
转移方程:(g[i][j]=g[i-1][j] + g[i-1][j-1]cdot A[i])
其中(g[i][0]=1)
也就是说当前这(j)个不合法的位置可能有两种方式得到:

  1. (j)个不合法的宝石都放在箱子(1—i-1)之间
  2. (j-1)个不合法的宝石放在在箱子(1—i-1)之间,还有一个不合法的宝石在(i)这个箱子中

最后得到的(g[n][j])就是(g_x)
这个状态转移方程都是从上一层得到的,可以内存优化,只用一维数组

//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
typedef long long int LL;
const LL MOD = 998244353;
const int MAXN = 8e3+7;
LL n,f[MAXN],A[MAXN],g[MAXN];
int main(){
    ____();
    f[0] = 1;
    for(int i = 1; i < MAXN; i++) f[i] = f[i-1] * i % MOD;
    cin >> n;
    for(int i = 1; i <= n; i++){
        int x; cin >> x;
        A[x]++;
    }
    // g[i][x]: 前i个盒子x个不合法的case
    // ret = sigma_{x=0}^{n} (-1)^x * g[x] * (n-x)!
    g[0] = 1;
    for(int i = 1; i <= n; i++) for(int j = i; j >= 1; j--) g[j] = (g[j] + g[j-1] * A[i]) % MOD;
    LL ret = 0;
    for(int i = 0, j = 1; i <= n; i++, j *= -1) ret = (ret + j * g[i] * f[n-i]) % MOD;
    cout << (ret+MOD)%MOD << endl;
    return 0;
}
原文地址:https://www.cnblogs.com/kikokiko/p/12945248.html