1. 程式人生 > >[BZOJ1079/Luogu2476][SCOI2008]著色方案

[BZOJ1079/Luogu2476][SCOI2008]著色方案

題目連結:

BZOJ1079

Luogu2476

記憶化搜尋+\(DP\)

首先看到資料範圍就知道狀態是個\(15^5\)的級別而不是\(5^{15}\)了。

那麼顯然設\(f_{a,b,c,d,e}\)表示還有\(a\)種顏色剩\(1\)個,\(b\)種剩兩個,\(\cdots\)\(e\)種剩\(5\)個時的方案數。

但是如何判斷兩個顏色不能相鄰呢?

如果記錄上一次的顏色,那麼這次判斷也需要顏色,則狀態爆炸。

發現無論上次是什麼顏色,只需要判斷是否相同即可。

那麼就記錄上一次選的顏色現在還剩幾個,在轉移時如果相同那麼方案數就要少一種選擇。

綜上,我們得到了如下的轉移式:

\[f_{a,b,c,d,e,PreRem}+=f_{a-1,b,c,d,e,0}*(a-[PreRem==1]) (a>0)\]

其中\(PreRem\)代表上一次選的顏色現在剩幾個。

如果還有\(a\)個顏色\(1\),那麼可以更新答案,\(PreRem\)變為\(0\),此時有\(a\)種顏色可選,故答案\(*a\)

同時如果\(PreRem==1\),那麼說明這\(a\)種顏色中有一種與之前重複,不能選,只剩\(a-1\)種顏色可選,答案\(*(a-1)\)

對於其他\(4\)項轉移方程也同理(別忘記在低項加上剛用掉的顏色)。

時間複雜度 \(O(k^c)\)

#include <cstdio>
#include <cstring>

int k,c[10];
int f[16][16][16][16][16][6];
const int Mod=1000000007;

int DP(int a,int b,int c,int d,int e,int PreRem)
{
    if(!a&&!b&&!c&&!d&&!e)return 1;
    int &Ans=f[a][b][c][d][e][PreRem];
    if(~Ans)return Ans;
    Ans=0;
    if(a)(Ans+=DP(a-1,b,c,d,e,0)*1LL*(PreRem==1?a-1:a)%Mod)%=Mod;
    if(b)(Ans+=DP(a+1,b-1,c,d,e,1)*1LL*(PreRem==2?b-1:b)%Mod)%=Mod;
    if(c)(Ans+=DP(a,b+1,c-1,d,e,2)*1LL*(PreRem==3?c-1:c)%Mod)%=Mod;
    if(d)(Ans+=DP(a,b,c+1,d-1,e,3)*1LL*(PreRem==4?d-1:d)%Mod)%=Mod;
    if(e)(Ans+=DP(a,b,c,d+1,e-1,4)*1LL*(PreRem==5?e-1:e)%Mod)%=Mod;
    return Ans;
}

int main()
{
    scanf("%d",&k),memset(f,-1,sizeof f);
    for(int cs;k--;)scanf("%d",&cs),++c[cs];
    printf("%d\n",DP(c[1],c[2],c[3],c[4],c[5],0));
    return 0;
}