Emiya 家今天的饭(CSP 2019 D2 T1)

题目

题目描述

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

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

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

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

这里的 \lfloor x \rfloor⌊x⌋ 为下取整函数,表示不超过 xx 的最大整数。

这些要求难不倒 Emiya,但他想知道共有多少种不同的符合要求的搭配方案。两种方案不同,当且仅当存在至少一道菜在一种方案中出现,而不在另一种方案中出现。

Emiya 找到了你,请你帮他计算,你只需要告诉他符合所有要求的搭配方案数对质数 998,244,353998,244,353 取模的结果。

输入格式

第 1 行两个用单个空格隔开的整数 n,mn,m。

第 2 行至第 n + 1n+1 行,每行 mm 个用单个空格隔开的整数,其中第 i + 1i+1 行的 mm 个数依次为 a_{i,1}, a_{i,2}, \cdots, a_{i,m}ai,1​,ai,2​,⋯,ai,m​。

输出格式

仅一行一个整数,表示所求方案数对 998,244,353998,244,353 取模的结果

输入输出样例

输入 #1复制

2 3 
1 0 1
0 1 1

输出 #1复制

3

输入 #2复制

3 3
1 2 3
4 5 0
6 0 0

输出 #2复制

190

输入 #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, ji,j,Emiya 都最多只会做一道菜,因此我们直接通过给出烹饪方法、主要食材的编号来描述一道菜。

符合要求的方案包括:

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

因此输出结果为 3 \mod 998,244,353 = 33mod998,244,353=3。 需要注意的是,所有只包含一道菜的方案都是不符合要求的,因为唯一的主要食材在超过一半的菜中出现,这不满足 Yazid 的要求。

【样例 2 解释】

Emiya 必须至少做 2 道菜。

做 2 道菜的符合要求的方案数为 100。

做 3 道菜的符合要求的方案数为 90。

因此符合要求的方案数为 100 + 90 = 190。

【数据范围】

Emiya 家今天的饭(CSP 2019 D2 T1)_第1张图片

对于所有测试点,保证 1 \leq n \leq 1001≤n≤100,1 \leq m \leq 20001≤m≤2000,0 \leq a_{i,j} \lt 998,244,3530≤ai,j​<998,244,353。

 

题解

首先这道题要找到一个很重要的性质:只可能有一种食材会超过在一半的菜中出现(考试时竟然没想到?)

所以这道题就可以用所有的情况减去不符合条件的情况来算符合条件情况

而所有的情况很好算:\prod _{i=1}^{i<=n}\sum_{j=1}^{j<=m}a[i][j]+1

加1是在sigma算完后加一

最后的积要减一(全都不拿的情况)

那么现在考虑不符合条件的情况,感觉还是要用计数dp做,但是这样的dp似乎很难进行设计

这里用到一种新方法

首先枚举是哪一种菜超过了限制,然后:

dp[i][j]表示选了前i个烹饪方法,最后达到的权值为j的方案数,权值的计数如下:

如果选择了超过了限制的菜,在权值+2

如果未在第i个方法里选择菜,则权值+1

否则,权值不变(也就是选择了其它菜)

 那么,最后只需要统计dp[n][n+1]到dp[n][2*n]的值的和即可

那么状态转移方程就可以直接写出来

dp[i][j] = dp[i-1][j]*(sum[i]-a[i][j]) + dp[i-1][j-1] + dp[i-1][j-2] * a[i][j]

代码:

#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
#define ll long long
const int MAXN = 103;
const int MAXM = 2003;
const ll mod = 998244353;
int n , m;
ll dp[MAXN][MAXN*3];
ll sum[MAXN] , a[MAXN][MAXM];
ll ans;

void read( ll &x ){
    x = 0;char s = getchar();
    while( s < '0' || s > '9' ) s = getchar();
    while( s >= '0' && s <= '9' ){
        x = x * 10 + s  - '0';
        s = getchar();
    }
}
int main(){
    scanf( "%d%d" , &n , &m );
    for( int i = 1 ; i <= n ; i ++ ){
        for( int j = 1 ; j <= m ; j ++ ){
            read( a[i][j] );
            sum[i] += a[i][j];
            sum[i] %= mod;
        }
    }
    ans = 1;
    for( int i = 1 ; i <= n ; i ++ ){
        ans = ans * ( ( sum[i] + 1 ) % mod ) % mod;
    }
    ans --;//统计总方案数
    for( int i = 1 ; i <= m ; i ++ ){
        memset(dp , 0 , sizeof( dp ) );
        dp[0][0] = 1;
        for( int j = 0 ; j <= n ; j ++ ){
            for( int k = 0 ; k <= 2 * n ; k ++ ){
                dp[j+1][k+1] = ( dp[j+1][k+1] + dp[j][k] ) % mod;
                dp[j+1][k] = ( dp[j+1][k] + dp[j][k] * (( sum[j+1] - a[j+1][i]) % mod ) % mod )% mod ;
                dp[j+1][k+2] = ( dp[j+1][k+2] + dp[j][k] * a[j+1][i] % mod ) % mod;
            }
        }
        for( int j = n + 1 ; j <= 2 * n ; j ++ )
            ans = ( ans - dp[n][j] + mod ) % mod;//减去不合法的
    }
    printf( "%lld" , ans );
    return 0;
}

 

你可能感兴趣的:(dp,动态规划,总结)