NOIP2015提高组第二轮 day2 - T1:Emiya 家今天的饭

题目链接

NOIP2015提高组第二轮 day2 - T1:Emiya 家今天的饭

题目描述

Emiya 是个擅长做菜的高中生,他共掌握 n n n烹饪方法,且会使用 m m m主要食材做菜。为了方便叙述,我们对烹饪方法从 1 ∼ n 1 \sim n 1n 编号,对主要食材从 1 ∼ m 1 \sim m 1m 编号。

Emiya 做的每道菜都将使用恰好一种烹饪方法与恰好一种主要食材。更具体地,Emiya 会做 a i , j a_{i,j} ai,j 道不同的使用烹饪方法 i i i 和主要食材 j j j 的菜( 1 ≤ i ≤ n 1 \leq i \leq n 1in 1 ≤ j ≤ m 1 \leq j \leq m 1jm),这也意味着 Emiya 总共会做 ∑ i = 1 n ∑ j = 1 m a i , j \sum\limits_{i=1}^{n} \sum\limits_{j=1}^{m} a_{i,j} i=1nj=1mai,j 道不同的菜。

Emiya 今天要准备一桌饭招待 Yazid 和 Rin 这对好朋友,然而三个人对菜的搭配有不同的要求,更具体地,对于一种包含 k k k 道菜的搭配方案而言:

  • Emiya 不会让大家饿肚子,所以将做至少一道菜,即 k ≥ 1 k \geq 1 k1
  • Rin 希望品尝不同烹饪方法做出的菜,因此她要求每道菜的烹饪方法互不相同
  • Yazid 不希望品尝太多同一食材做出的菜,因此他要求每种主要食材至多在一半的菜(即 ⌊ k 2 ⌋ \lfloor \frac{k}{2} \rfloor 2k 道菜)中被使用

这里的 ⌊ 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 取模的结果。

样例 #1

样例输入 #1

2 3 
1 0 1
0 1 1

样例输出 #1

3

样例 #2

样例输入 #2

3 3
1 2 3
4 5 0
6 0 0

样例输出 #2

190

样例 #3

样例输入 #3

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

样例输出 #3

742

提示

【样例 1 解释】

由于在这个样例中,对于每组 i , j i, j i,j,Emiya 都最多只会做一道菜,因此我们直接通过给出烹饪方法、主要食材的编号来描述一道菜。

符合要求的方案包括:

  • 做一道用烹饪方法 1、主要食材 1 的菜和一道用烹饪方法 2、主要食材 2 的菜
  • 做一道用烹饪方法 1、主要食材 1 的菜和一道用烹饪方法 2、主要食材 3 的菜
  • 做一道用烹饪方法 1、主要食材 3 的菜和一道用烹饪方法 2、主要食材 2 的菜

因此输出结果为 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 912 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 1316 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 1721 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 2225 100 100 100 2 × 1 0 3 2\times 10^3 2×103 998244353 998244353 998244353

对于所有测试点,保证 1 ≤ n ≤ 100 1 \leq n \leq 100 1n100 1 ≤ m ≤ 2000 1 \leq m \leq 2000 1m2000 0 ≤ a i , j < 998 , 244 , 353 0 \leq a_{i,j} \lt 998,244,353 0ai,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 道菜的搭配方案要满足如下条件:

  • 至少做一道菜,即 k ≥ 1 k \geq 1 k1
  • 烹饪方法互不相同,即 k ≤ n k \le n kn
  • 每种主要食材至多在一半的菜(即 ⌊ k 2 ⌋ \lfloor \frac{k}{2} \rfloor 2k 道菜)中被使用

直接求符合所有要求的搭配方案数很困难,这里可以采用补集的思想,首先求解满足前 2 2 2个条件的方案总数,然后减去不满足第 3 3 3个条件的方案。

满足前2个条件的方案

下面使用动态规划的思想求解满足前 2 2 2个条件的方案总数

  • 状态f[i][k]表示使用前i种烹饪方法做出k道菜的方案数,其中 1 ≤ k ≤ n 1\le k\le n 1kn。方案总数为 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 1jm。因此在前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 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防止数组越界。

时间复杂度

  • 求解满足前2个条件的时间复杂度
    • 状态数为 O ( n × n ) O(n\times n) O(n×n)
    • 使用s[i] = a[i][1] + a[i][2] + ... + a[i][m]优化后,状态计算的时间复杂度为 O ( 1 ) O(1) O(1)
  • 求解不满足第3个条件时间复杂度
    • 枚举不合理的食材k,时间复杂度为 O ( m ) O(m) O(m)
    • 状态数为 O ( n × n ) O(n\times n) O(n×n)

最终时间复杂度为 O ( n 2 m ) = 2 × 1 0 7 O(n^2m)=2\times10^7 O(n2m)=2×107

代码实现

#include <iostream>
#include <cstring>
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;
    
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

少儿编程乔老师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值