洛谷5339 BZOJ5510 TJOI2019 唱、跳、rap和籃球 容斥 dp 組合數

題目鏈接

題意:
給你一個nn,表示你要選出一個nn個人組成的隊列。有四種不同的人,人數分別是a,b,c,da,b,c,d個,保證a+b+c+d>=na+b+c+d>=n。我們要求隊伍裏不能有連續的四個位置依次是第一種人、第二種人、第三種人和第四種人。求方案數。對998244353998244353取模。n<=1000,a,b,c,d<=500n<=1000,a,b,c,d<=500

題解:
難得自己做出來道題。

首先先考慮一些暴力的dp想法。我們可能會想記錄當前這四種人分別站到隊伍裏了多少個,然後當前已經有了這四種人中連續的幾種人的方案數。很顯然的是,空間和時間都不允許。

於是我們換個思路,我們直接做不好做,我們就考慮用總方案數減去不合法的方案數。我們先考慮如何求總方案數。我們設dp[i][j]dp[i][j]表示考慮了前ii種人,已經佔了jj個位置的方案數。在轉移時,我們並不能直接累加,因爲我們在隊伍裏放進去若干個當前這種人的時候,位置是可以任意選的,所以要乘上一個組合數。我們設第ii種人可以用num[i]num[i]個,那麼可以列出轉移方程式:dp[i][j]=k=max(0,jnum[i])jdp[i1][k]Cnjjkdp[i][j]=\sum_{k=max(0,j-num[i])}^{j}dp[i-1][k]*C_{n-j}^{j-k} 這樣我們就可以在O(n2)O(n^2)的時間內算出這個總方案數了。

接下來我們考慮減去不合法的方案,我們發現可以容斥,我們用總方案數減去至少有一處出現不合法,其餘位置隨便放的方案數,加上至少有兩處不合法,其他位置隨便放的方案數,以此類推。我們已經會算xx個位置,每種人可以用的數量已知的情況下怎麼算總方案數,那麼我們就考慮如何算有nn個位置,選出kk組連續的四個位置的方案數。這個其實也是一個經典的組合數模型,我們可以預先對於每一組放上三個,這樣就變成了Cn3kkC_{n-3*k}^{k}了。這樣就可以計算出答案了。

複雜度的話,是O(n3)O(n^3)的,實際來分析一下常數,應該會發現容斥的枚舉次數上限是n/4n/4,內層的話要枚舉44次,每次枚舉一個當前佔了的位置數和上一次佔了的位置數,這兩個的差不超過num[i]num[i],實際常數應該是不大的。再加上時限是44秒,就這麼跑過去了,而且實測的話,似乎最慢的點也不到一秒。

update:
之前的代碼在BZOJ上會被卡常,新的代碼減少了取模次數,是可以過的。

代碼:

#include <bits/stdc++.h>
using namespace std;
 
int n,a,b,c,d,shu[5];
const long long mod=998244353;
long long ans,dp[5][1010],C[1010][1010];
int main()
{
    scanf("%d%d%d%d%d",&n,&a,&b,&c,&d);
    C[0][0]=1;
    for(int i=1;i<=n;++i)
    {
        C[i][0]=1;
        for(int j=1;j<=i;++j)
        C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
    }
    int mn=min(a,b);
    mn=min(mn,c);
    mn=min(mn,d);
    mn=min(mn,n/4);
    for(int i=0;i<=mn;++i)
    {
        long long opt=1;
        if(i&1)
        opt=-1;
        memset(dp,0,sizeof(dp));
        shu[1]=a-i;
        shu[2]=b-i;
        shu[3]=c-i;
        shu[4]=d-i;
        for(int j=0;j<=shu[1];++j)
        dp[1][j]=C[n-4*i][j];
        for(int j=2;j<=4;++j)
        {
            for(int k=0;k<=n;++k)
            {
                for(int l=max(0,k-shu[j]);l<=k&&n-4*i-l>=0;++l)
                dp[j][k]=(dp[j][k]+dp[j-1][l]*C[n-4*i-l][k-l])%mod;
            }
        }
        ans=(ans+opt*dp[4][n-4*i]*C[n-3*i][i]%mod+mod)%mod;
    }
    printf("%lld\n",ans);
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章