NOIP2015提高组第二轮 day2 - T1:Emiya 家今天的饭
Emiya 是个擅长做菜的高中生,他共掌握 n n n 种烹饪方法,且会使用 m m m 种主要食材做菜。为了方便叙述,我们对烹饪方法从 1 ∼ n 1 \sim n 1∼n 编号,对主要食材从 1 ∼ m 1 \sim m 1∼m 编号。
Emiya 做的每道菜都将使用恰好一种烹饪方法与恰好一种主要食材。更具体地,Emiya 会做 a i , j a_{i,j} ai,j 道不同的使用烹饪方法 i i i 和主要食材 j j j 的菜( 1 ≤ i ≤ n 1 \leq i \leq n 1≤i≤n、 1 ≤ j ≤ m 1 \leq j \leq m 1≤j≤m),这也意味着 Emiya 总共会做 ∑ i = 1 n ∑ j = 1 m a i , j \sum\limits_{i=1}^{n} \sum\limits_{j=1}^{m} a_{i,j} i=1∑nj=1∑mai,j 道不同的菜。
Emiya 今天要准备一桌饭招待 Yazid 和 Rin 这对好朋友,然而三个人对菜的搭配有不同的要求,更具体地,对于一种包含 k k k 道菜的搭配方案而言:
这里的 ⌊ x ⌋ \lfloor x \rfloor ⌊x⌋ 为下取整函数,表示不超过 x x x 的最大整数。
这些要求难不倒 Emiya,但他想知道共有多少种不同的符合要求的搭配方案。两种方案不同,当且仅当存在至少一道菜在一种方案中出现,而不在另一种方案中出现。
Emiya 找到了你,请你帮他计算,你只需要告诉他符合所有要求的搭配方案数对质数 998 , 244 , 353 998,244,353 998,244,353 取模的结果。
第 1 行两个用单个空格隔开的整数 n , m n,m n,m。
第 2 行至第 n + 1 n + 1 n+1 行,每行 m m m 个用单个空格隔开的整数,其中第 i + 1 i + 1 i+1 行的 m m m 个数依次为 a i , 1 , a i , 2 , ⋯ , a i , m a_{i,1}, a_{i,2}, \cdots, a_{i,m} ai,1,ai,2,⋯,ai,m。
仅一行一个整数,表示所求方案数对 998 , 244 , 353 998,244,353 998,244,353 取模的结果。
2 3
1 0 1
0 1 1
3
3 3
1 2 3
4 5 0
6 0 0
190
5 5
1 0 0 1 1
0 1 0 1 0
1 1 1 1 0
1 0 1 0 1
0 1 1 0 1
742
【样例 1 解释】
由于在这个样例中,对于每组 i , j i, j i,j,Emiya 都最多只会做一道菜,因此我们直接通过给出烹饪方法、主要食材的编号来描述一道菜。
符合要求的方案包括:
因此输出结果为 3 m o d 998 , 244 , 353 = 3 3 \bmod 998,244,353 = 3 3mod998,244,353=3。 需要注意的是,所有只包含一道菜的方案都是不符合要求的,因为唯一的主要食材在超过一半的菜中出现,这不满足 Yazid 的要求。
【样例 2 解释】
Emiya 必须至少做 2 道菜。
做 2 道菜的符合要求的方案数为 100。
做 3 道菜的符合要求的方案数为 90。
因此符合要求的方案数为 100 + 90 = 190。
【数据范围】
测试点编号 | n = n= n= | m = m= m= | a i , j < a_{i,j}< ai,j< | 测试点编号 | n = n= n= | m = m= m= | a i , j < a_{i,j}< ai,j< |
---|---|---|---|---|---|---|---|
1 1 1 | 2 2 2 | 2 2 2 | 2 2 2 | 7 7 7 | 10 10 10 | 2 2 2 | 1 0 3 10^3 103 |
2 2 2 | 2 2 2 | 3 3 3 | 2 2 2 | 8 8 8 | 10 10 10 | 3 3 3 | 1 0 3 10^3 103 |
3 3 3 | 5 5 5 | 2 2 2 | 2 2 2 | 9 ∼ 12 9\sim 12 9∼12 | 40 40 40 | 2 2 2 | 1 0 3 10^3 103 |
4 4 4 | 5 5 5 | 3 3 3 | 2 2 2 | 13 ∼ 16 13\sim 16 13∼16 | 40 40 40 | 3 3 3 | 1 0 3 10^3 103 |
5 5 5 | 10 10 10 | 2 2 2 | 2 2 2 | 17 ∼ 21 17\sim 21 17∼21 | 40 40 40 | 500 500 500 | 1 0 3 10^3 103 |
6 6 6 | 10 10 10 | 3 3 3 | 2 2 2 | 22 ∼ 25 22\sim 25 22∼25 | 100 100 100 | 2 × 1 0 3 2\times 10^3 2×103 | 998244353 998244353 998244353 |
对于所有测试点,保证 1 ≤ n ≤ 100 1 \leq n \leq 100 1≤n≤100, 1 ≤ m ≤ 2000 1 \leq m \leq 2000 1≤m≤2000, 0 ≤ a i , j < 998 , 244 , 353 0 \leq a_{i,j} \lt 998,244,353 0≤ai,j<998,244,353。
根据题目描述, Emiya掌握 n n n 种烹饪方法,能够使用 m m m 种主要食材做菜。第 i i i种烹饪方法使用第 j j j种食材,可以做出 a i , j a_{i,j} ai,j 道不同的菜。对于一种包含 k k k 道菜的搭配方案要满足如下条件:
直接求符合所有要求的搭配方案数很困难,这里可以采用补集的思想,首先求解满足前 2 2 2个条件的方案总数,然后减去不满足第 3 3 3个条件的方案。
下面使用动态规划的思想求解满足前 2 2 2个条件的方案总数:
状态f[i][k]
表示使用前i
种烹饪方法做出k
道菜的方案数,其中 1 ≤ k ≤ n 1\le k\le n 1≤k≤n。方案总数为 f [ n ] [ 1 ] + f [ n ] [ 2 ] + . . . + f [ n ] [ n ] f[n][1]+f[n][2]+...+f[n][n] f[n][1]+f[n][2]+...+f[n][n],即 ∑ k = 1 n f [ n ] [ k ] \sum_{k=1}^nf[n][k] ∑k=1nf[n][k]。
状态计算以每种烹饪方法为阶段,对于第i
种烹饪方法有不使用和使用两种情况:
不使用第i
种烹饪方法,那么方案数为 f[i-1][k]
使用第 i
种烹饪方法,根据不同的食材又可以做 a i , j a_{i,j} ai,j 道不同的菜,其中 1 ≤ j ≤ m 1\le j \le m 1≤j≤m。因此在前i-1
种烹饪方法做出k-1
道菜后,可以分为 m m m种情况:
i
种烹饪方法和第1
种食材做出第k
道菜,方案数为f[i-1][k-1] * a[i][1]
i
种烹饪方法和第2
种食材做出第k
道菜,方案数为f[i-1][k-1] * a[i][2]
i
种烹饪方法和第m
种食材做出第k
道菜,方案数为f[i-1][k-1] * a[i][m]
使用第
i
种烹饪方法的方案数为:f[i-1][k-1] * a[i][1] + f[i-1][k-1] * a[i][2] + ... + f[i-1][k-1] * a[i][m]
。不妨设s[i] = a[i][1] + a[i][2] + ... + a[i][m]
,那么方案数为f[i-1][k-1]*s[i]
。
因此状态转移方程f[i][k] = f[i-1][k] + f[i - 1][k - 1] * s[i]
初始状态:f[0][0] = 1
第 3 3 3个条件为每种主要食材至多在一半的菜(即 ⌊ k 2 ⌋ \lfloor \frac{k}{2} \rfloor ⌊2k⌋ 道菜)中被使用,也就是说不合法的方案中有且只有一种食材的数量大于其它食材的总和。
这样就用状态 g [ i ] [ u ] [ v ] g[i][u][v] g[i][u][v]表示对于前 i i i种烹饪方法,不合理的食材选了 u u u种,其它食材一共选了 v v v种的方案总数,那么不合理的方案数为 ∑ g [ i ] [ u ] [ v ] ( u > v ) \sum g[i][u][v](u>v) ∑g[i][u][v](u>v)。
通过上述分析可以发现,我们关心的只是不合理的食材数量和其它食材数量的差值。那么不妨设g[i][j]
表示计算前i
种烹饪方法中,使用的不合理的食材数量和其它食材数量的差值为j
的方案数,即可降低g[]
数组的维度。
g[i][j]
表示前i
种烹饪方法中,使用的不合理的食材数量和其它食材数量的差值为j
的方案数首先,枚举不合理的食材k
:
i
种烹饪方法的方案数为:g[i - 1][j]
i
种烹饪方法的方案数,并且选择不合理的食材k
的方案数:g[i-1][j-1]*a[i][k]
。
选择
k
后,差值为j
,那么之前状态的差值为j-1
。
i
种烹饪方法的方案数,并且不选择食材k
的方案数:g[i-1][j+1]*s[i]-a[i][k]
不选
k
后,差值为j
,那么之前状态的差值为j+1
。
注意:注意做差可能为负数,因此可以把所有状态加一个偏移量 n n n防止数组越界。
s[i] = a[i][1] + a[i][2] + ... + a[i][m]
优化后,状态计算的时间复杂度为 O ( 1 ) O(1) O(1)k
,时间复杂度为 O ( m ) O(m) O(m)最终时间复杂度为 O ( n 2 m ) = 2 × 1 0 7 O(n^2m)=2\times10^7 O(n2m)=2×107
#include
#include
using namespace std;
typedef long long LL;
const int N = 110, M = 2010, MOD = 998244353;
//对于g数组,加上偏移量n,防止求差数组越界
int a[N][M], s[N], f[N][N], g[N][N * 2];
int main()
{
int n, m;
cin >> n >> m;
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= m; j ++)
{
cin >> a[i][j];
s[i] = (s[i] + a[i][j]) % MOD;
}
//计算满足前两个条件的方案数
f[0][0] = 1;
for(int i = 1; i <= n; i ++)
for(int k = 0; k <= n; k ++)
{
f[i][k] = f[i - 1][k]; //不使用第i种烹饪方法
if(k > 0)
f[i][k] = (f[i][k] + (LL)f[i - 1][k - 1] * s[i]) % MOD; //使用第i中烹饪方法的方案数
}
LL ans = 0; //求满足前2个条件的方案总数
for(int i = 1; i <= n; i ++)
ans = (ans + f[n][i]) % MOD;
//从总方案数ans中去掉不满足条件3的方案
//枚举不合理的食材k
for(int k = 1; k <= m; k ++)
{
memset(g, 0, sizeof g);
g[0][n] = 1; //加上偏移量n
for(int i = 1; i <= n; i ++)
//枚举食材数量的差值
for(int j = 1; j <= n + i; j ++)
{
g[i][j] = (g[i][j] + g[i - 1][j]) % MOD;
g[i][j] = (g[i][j] + (LL)g[i - 1][j - 1] * a[i][k]) % MOD;
g[i][j] = (g[i][j] + (LL)g[i - 1][j + 1] * (s[i] - a[i][k])) % MOD;
}
//从总方案中减去不满足要求的方案,即差值 > n
for(int i = n + 1; i <= n * 2; i ++)
ans = (ans - g[n][i] + MOD) % MOD; //防止出现负值
}
cout << ans;
return 0;
}