[BZOJ1079/Luogu2476][SCOI2008]着色方案
题目链接:
记忆化搜索+\(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;
}