[CSP 2019]Emiya家今天的饭[dp][容斥原理]

题面:https://loj.ac/problem/3211


根据题面描述,一种方案会被两个限制所限制,分别是:任意一行至多只能被选择一个;任意一列选择的物品的数量不能超过总数量的一半

可以发现求满足这两个限制的方案数很难,我们可以考虑将他们拆开

根据容斥原理,答案可以变成 任意一行至多被选一个的方案数 $-$ 任意一行至多被选一个且存在一列被选择的物品数量超过总数的一半的方案数 

这样就好做多了吧


那么怎么计算 任意一行至多被选一个且存在一列被选择的物品数量超过总数的一半的方案数  呢?

可以发现选择的物品数量超过总数的一半的列有且仅有一个,那么我们不妨来枚举这一列,分别计算出方案数

我们现在假设被选物品数超过一半的列是 $p$,我们可以 dp 求解

设 $f[i][j][k]$ 表示前 $i$ 行, $p$ 列被选了 $j$ 个,其他列被选了 $k$ 个的方案数,考虑转移

转移有三种情况分别是这一行不选,这一行选位于 p 列的物品,这一行不选位于 p 列的物品

$$f[i][j][k]=f[i-1][j][k]+a[i][p]\times f[i-1][j-1][k]+(sum[i]-a[i][p])\times f[i-1][j][k-1]$$

至于 任意一行至多被选一个的方案数,直接将数量都乘起来就好,不过需要注意不能不选,所以需要把乘起来的积减一

总复杂度 $O(mn^3)$,可以获得 84 分的好成绩


转移是 $O(1)$ 的,所以我们考虑能否优化掉一维状态

可以发现我们其实不关心 $j,\ k$ 这两维的具体取值,我们只关心 $j$ 与 $k$ 的差值(是否大于 0)

所以我们可以尝试把这两维缩掉:设 $f[i][j]$ 表示前 $i$ 行  任意一行至多被选一个的方案数  减去  任意一行至多被选一个且存在一列被选择的物品数量超过总数的一半的方案数  为 $j$ 的答案

转移完全一样:

$$f[i][j]=f[i-1][j]+a[i][p]\times f[i-1][j-1]+(sum[i]-a[i][p])\times f[i-1][j+1]$$

注意 $j$ 可能是负数

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cstring>
 4 #include <algorithm>
 5 #include <cstdlib>
 6 #include <cctype>
 7 #include <vector>
 8 #define ll long long
 9 #define N 110
10 #define M 2010
11 
12 namespace chiaro {
13 
14 template <class T>
15 inline void read(T& num) {
16     num = 0; register char c = getchar(), up = c;
17     while(!isdigit(c)) up = c, c = getchar();
18     while(isdigit(c)) num = (num << 1) + (num << 3) + (c ^ '0'), c = getchar();
19     up == '-' ? num = -num : 0;
20 }
21 template <class T>
22 inline void read(T& a, T& b) {read(a); read(b);}
23 template <class T>
24 inline void read(T& a, T& b, T& c) {read(a); read(b); read(c);}
25 template <class T>
26 void print(T num) {(num > 9) ? (print(num / 10), 0) : 0; putchar(num % 10 + '0'); return;}
27 
28 const ll ha = 998244353;
29 #define add(a, b)  ((0ll + a) + (b) >= ha ? (0ll + a) + (b) - ha : (0ll + a) + (b))
30 #define minus(a,b) ((0ll + a) - (b) < 0 ? (0ll + a) - (b) + ha : (0ll + a) - (b))
31 
32 int n, m;
33 int a[N][M];
34 ll f[N][M << 1];
35 int sum[N][M];
36 int tot[N];
37 ll ans;
38 
39 inline void setting() {
40 #ifdef ONLINE_JUDGE
41     freopen("meal.in", "r", stdin);
42     freopen("meal.out", "w", stdout);
43 #endif
44 }
45 
46 #define id(x) ((x) + n)
47 
48 inline int main () {
49     setting(); read(n, m); ans = 1;
50     for(register int i = 1; i <= n; ++i) for(register int j = 1; j <= m; ++j) read(a[i][j]);
51     for(register int i = 1; i <= n; ++i) for(register int j = 1; j <= m; ++j) tot[i] = add(tot[i], a[i][j]);
52     for(register int i = 1; i <= n; ++i) ans = (ans * (tot[i] + 1) % ha);
53     for(register int i = 1; i <= n; ++i) for(register int j = 1; j <= m; ++j) sum[i][j] = minus(tot[i], a[i][j]);
54     ans = minus(ans, 1);
55     for(register int p = 1; p <= m; ++p) {
56         for(register int i = 0; i <= n; ++i) for(register int j = 0; j <= (n << 1); ++j) f[i][j] = 0;
57         f[0][id(0)] = 1;
58         for(register int i = 1; i <= n; ++i) {
59             for(register int j = -i; j <= i; ++j) 
60                 f[i][id(j)] = (f[i - 1][id(j)] + a[i][p] * f[i - 1][id(j - 1)] % ha + sum[i][p] * f[i - 1][id(j + 1)] % ha) % ha;
61         }
62         for(register int i = 1; i <= n; ++i) ans = minus(ans, f[n][id(i)]);
63     }
64     printf("%lld\n", ans);
65     return 0;
66 }
67 
68 }
69 
70 signed main () {return chiaro::main();} 

 

posted @ 2020-08-26 18:42  Chiaro  阅读(180)  评论(0)    收藏  举报