[SCOI2008]着色方案
因为这类问题都需要从前一步来推后一步,所以大概率是DP类问题
首先我们需要确定状态,如果把每种颜色都当成一维来记录的话,最大是\(15\) 维肯定是不可取的,所以就要考虑别的状态
因为每种颜色\(c_i\leq5\) ,我们可以把每种颜色剩余能涂的个数看成一个等价类 来确定每种状态
\(f[c1][c2][c3][c4][c5][lst]\) 其中,\(c1\) 表示在所有颜色中剩余能涂的个数为\(1\) 的,有\(c1\) 个颜色(颜色之间有相同,也有不同)
\(c2,c3,c4,c5\) 同理,\(lst==last\) 代表上一次用的是那种等价类里面的颜色
-
如果把不同颜色,按照剩余能涂的个数来划分到一个等价类,会不会对答案造成影响?,不会
-
因为我们每一步取的颜色,对于它来说,肯定会改变它的状态,所以每一步取的颜色肯定都不相同
例如 当前有\(4\) 种颜色\(a,b,c,d\) 它们都剩余了\(5\) 个,所以 \(c5==4\) 这些颜色一共\(20\) 个 ,其中\(c1,c2,c3,c4,c5\) 是相互独立的
我们从\(c5\) 中拿出一个颜色(可以不知道取的是那种颜色)之后\(c5--\) ,\(c4++\) 因为其中有一种颜色(我们不关心掉下去的颜色是谁,只关心这个等价类)从原先的剩余\(5\) 个变成了\(4\) 个
这就是他们相互独立的原因,也是可以这样分类来取的原因。还要记得判断一下\(lst\)
if(c2) res += (c2 - (lst == 3)) * dfs(c1 + 1,c2 - 1,c3,c4,c5,2);
这段代码的意思是,如果\(c2\) 还能取,就把这种情况加到\(res\) 中,如果上次取的是\(c3\) 那么就会从\(c3\) 中掉一种颜色到\(c2\) 所以,我们要避开这种相同颜色,只能取剩下的\(c2-1\) 种了,取了\(c2\) 种后,下一步就是\(c1++,c2--,lst=2\) 然后根据乘法原理搞就行
最后相加取模即可
if(c5) res += c5 * dfs(c1,c2,c3,c4 + 1,c5 - 1,5);
因为不可能有\(c6\) 所以就不用考虑\(lst\) 的影响了,直接搞
#include <bits/stdc++.h>
using namespace std;
const int N = 16,mod = 1e9 + 7;
typedef long long LL;
int f[N][N][N][N][N][6],k,c[N],x,n;
LL dfs(int c1,int c2,int c3,int c4,int c5,int lst) {
// 如果这种状态被记录过,直接返回即可,不用计算
if(f[c1][c2][c3][c4][c5][lst]) return f[c1][c2][c3][c4][c5][lst];
LL res = 0;
if(c1) res += (c1 - (lst == 2)) * dfs(c1 - 1,c2,c3,c4,c5,1);
if(c2) res += (c2 - (lst == 3)) * dfs(c1 + 1,c2 - 1,c3,c4,c5,2);
if(c3) res += (c3 - (lst == 4)) * dfs(c1,c2 + 1,c3 - 1,c4,c5,3);
if(c4) res += (c4 - (lst == 5)) * dfs(c1,c2,c3 + 1,c4 - 1,c5,4);
if(c5) res += c5 * dfs(c1,c2,c3,c4 + 1,c5 - 1,5);
return f[c1][c2][c3][c4][c5][lst] = res % mod;
}
int main() {
// 边界条件
for(int i = 1;i <= 5; ++i) f[0][0][0][0][0][i] = 1;
cin >> n;
for(int i = 1;i <= n; ++i) {
cin >> x;// 读入第 i 种颜色的个数
c[x] ++;// 能涂 i 个木块的颜色一共有 c[x] 个
}
cout << dfs(c[1],c[2],c[3],c[4],c[5],0) << endl;
return 0;
}