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。

【數據範圍】

對於所有測試點,保證 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 <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
#include <algorithm>
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;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章