有n个木块排成一行,从左到右依次编号为1~n。你有k种颜色的油漆,其中第i种颜色的油漆足够涂ci个木块。
所有油漆刚好足够涂满所有木块,即c1+c2+...+ck=n。相邻两个木块涂相同色显得很难看,所以你希望统计任意两
个相邻木块颜色不同的着色方案。
100%的数据满足:1 <= k <= 15, 1 <= ci <= 5
题解
莫种意义上也算是一道dp好题吧,虽然说不难,但是转移很有意思。
注意到直接状压是5 ^ 15的必炸,而15 ^ 5比较靠谱。
考虑如果两个颜色可以涂的次数相等,那么它们是等价的。所以f[x1][x2][x3][x4][x5][k]表示可涂i次的颜色的数量为xi,上一个涂的颜色是k。其余细节见代码注释。
#include<set>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
inline int read() {
int x = 0, flag = 1; char ch = getchar();
while (ch > '9' || ch < '0') { if (ch == '-') flag = -1; ch = getchar(); }
while (ch <= '9' && ch >= '0') { x = x * 10 + ch - '0'; ch = getchar(); }
return x * flag;
}
#define rep(ii, aa, bb) for (int ii = aa; ii <= bb; ii++)
#define drp(ii, aa, bb) for (int ii = aa; ii >= bb; ii--)
#define N 1000001
#define ll long long
#define ha 1000000007
int n, a[6];
ll f[16][16][16][16][16][6];
int vis[16][16][16][16][16][6];
ll dp(int x1, int x2, int x3, int x4, int x5, int k) {
ll &ans = f[x1][x2][x3][x4][x5][k];
if (vis[x1][x2][x3][x4][x5][k]) return ans;
if ((x1 | x2 | x3 | x4 | x5) == 0) return 1;
if (x1) ans += (x1 - (k == 2)) * dp(x1 - 1, x2, x3, x4, x5, 1); //考虑枚举这次选哪种剩余次数的颜色,一共有多少个剩余这种次数的个数就有多少种选择
if (x2) ans += (x2 - (k == 3)) * dp(x1 + 1, x2 - 1, x3, x4, x5, 2); //如果上次填的是颜色剩余次数为2的,意味着颜色中剩余次数为1的多了一个,那么这一次并不能再选这种颜色,这次可以选填1的就要少1。
if (x3) ans += (x3 - (k == 4)) * dp(x1, x2 + 1, x3 - 1, x4, x5, 3);
if (x4) ans += (x4 - (k == 5)) * dp(x1, x2, x3 + 1, x4 - 1, x5, 4);
if (x5) ans += x5 * dp(x1, x2, x3, x4 + 1, x5 - 1, 5);
vis[x1][x2][x3][x4][x5][k] = 1;
return ans %= ha;
}
int main() {
n = read();
rep(i, 1, n) a[read()]++;
cout << dp(a[1], a[2], a[3], a[4], a[5], 0);
return 0;
}