因为这类问题都需要从前一步来推后一步,所以大概率是DP类问题
首先我们需要确定状态,如果把每种颜色都当成一维来记录的话,最大是(15) 维肯定是不可取的,所以就要考虑别的状态
因为每种颜色(c_ileq5) ,我们可以把每种颜色剩余能涂的个数看成一个等价类 来确定每种状态
(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;
}