[CSP-S2019]Emiya 家今天的飯 題解


CSP-S2 2019 D2T1

很不錯的一題DP,通過這道題學到了很多。

身為一個對DP一竅不通的蒟蒻,在考場上還掙扎了1h來推式子,居然還有幾次幾乎推出正解,然而最后還是只能打個32分的暴搜滾粗


題意分析

給出一個矩陣,要求每行只能選一個節點,每列選的節點不能超過所有選的節點的一半,不能不選,給出每個節點的選擇方案數,求總方案數

思路分析

可以看出,維護每列已選的節點復雜度太大,不太可行;因此很容易想到,先不考慮每列不超過一半的這個限制,求出總方案數,然后再減去考慮這個限制后不合法的方案數。現在問題就變成,求任意列選的節點超過所有選的節點的一半的方案數之和。

顯然,在一個方案中,只可能有一列的節點超過所有選的節點的一半。因此可以想到枚舉這個超過限制的列,然后對於這個列進行DP求解。

具體實現

設$f_{i,j,k}$表示前$i$行選$j$個節點,當前枚舉到的列選$k$個節點的方案數。對於每個列,復雜度為$O(n^3)$,總的復雜度為$O(mn^3)$,可以得到84分的高分。

想得到滿分還需要進一步優化。考慮將某兩個狀態合並。觀察狀態,實際上我們想知道的只是$j,k$的大小關系,對於具體的值並不關心,考慮將它們合並到一維。

考慮我們需要的限制條件$k>\left \lfloor \frac{j}{2} \right \rfloor$,變形一下可以得到$2k+n-j>n$。觀察這個式子,可以發現,$n-j$就是這$n$行里沒有選的行數。然后一個奇妙的想法就出來了,對於每個節點,選它時當做該列選了兩次,而對於某一行不選時,當做所有列選了一次,最終要找的就是當前列被選超過$n$次的方案。這樣就成功地優化掉了第二維。

給一下狀態轉移方程:

f[j][k]=(f[j][k]+f[j-1][k]*(cnt[j]-w[j][i]))%P;//不選當前列
f[j][k+1]=(f[j][k+1]+f[j-1][k])%P;//不選當前行
f[j][k+2]=(f[j][k+2]+f[j-1][k]*w[j][i])%P;//選當前行當前列對應的節點

注意取模時出現負數的情況,記得開long long。

#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;
const int N=200,M=3000,P=998244353;//FFT(霧
int n,m;
ll ans=1;
ll cnt[N],w[N][M],f[N][M];
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
            scanf("%lld",&w[i][j]),cnt[i]=(cnt[i]+w[i][j])%P;
        ans=(ans*(cnt[i]+1))%P;//計算全部答案
    }
    ans=(ans+P-1)%P;//減去全部不選的情況
    for(int i=1;i<=m;i++)
    {
        memset(f,0,sizeof(f));
        f[0][0]=1;//DP初值
        for(int j=1;j<=n;j++)
            for(int k=0;k<=2*(j-1);k++)
            {
                f[j][k]=(f[j][k]+f[j-1][k]*(cnt[j]-w[j][i]))%P;
                f[j][k+1]=(f[j][k+1]+f[j-1][k])%P;
                f[j][k+2]=(f[j][k+2]+f[j-1][k]*w[j][i])%P;
            }
        for(int j=n+1;j<=2*n;j++)
            ans=(ans+P-f[n][j])%P;//減去當前枚舉到的不合法方案
    }
    printf("%lld",ans);
    return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM