題意:
給你一個,表示你要選出一個個人組成的隊列。有四種不同的人,人數分別是個,保證。我們要求隊伍裏不能有連續的四個位置依次是第一種人、第二種人、第三種人和第四種人。求方案數。對取模。
題解:
難得自己做出來道題。
首先先考慮一些暴力的dp想法。我們可能會想記錄當前這四種人分別站到隊伍裏了多少個,然後當前已經有了這四種人中連續的幾種人的方案數。很顯然的是,空間和時間都不允許。
於是我們換個思路,我們直接做不好做,我們就考慮用總方案數減去不合法的方案數。我們先考慮如何求總方案數。我們設表示考慮了前種人,已經佔了個位置的方案數。在轉移時,我們並不能直接累加,因爲我們在隊伍裏放進去若干個當前這種人的時候,位置是可以任意選的,所以要乘上一個組合數。我們設第種人可以用個,那麼可以列出轉移方程式: 這樣我們就可以在的時間內算出這個總方案數了。
接下來我們考慮減去不合法的方案,我們發現可以容斥,我們用總方案數減去至少有一處出現不合法,其餘位置隨便放的方案數,加上至少有兩處不合法,其他位置隨便放的方案數,以此類推。我們已經會算個位置,每種人可以用的數量已知的情況下怎麼算總方案數,那麼我們就考慮如何算有個位置,選出組連續的四個位置的方案數。這個其實也是一個經典的組合數模型,我們可以預先對於每一組放上三個,這樣就變成了了。這樣就可以計算出答案了。
複雜度的話,是的,實際來分析一下常數,應該會發現容斥的枚舉次數上限是,內層的話要枚舉次,每次枚舉一個當前佔了的位置數和上一次佔了的位置數,這兩個的差不超過,實際常數應該是不大的。再加上時限是秒,就這麼跑過去了,而且實測的話,似乎最慢的點也不到一秒。
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;
}