[SCOI2008] 着色方案
-
题目来源:山东省选 2008,CCF;
-
在线评测地址:Luogu#2476,BZOJ#1079;
-
运行限制:(1.00 ext{ms}/128 ext{MiB})。
题目描述
有 (n) 个木块排成一行,从左到右依次编号为 (1) 到 (n)。你有 (k) 种颜色的油漆,其中第 (i) 种颜色的油漆足够涂 (c_i) 个木块。所有油漆刚好足够涂满所有木块,即 (sum c_i=n)。
由于相邻两个木块涂相同色显得很难看,所以你希望统计任意两个相邻木块颜色不同的着色方案。
输入格式
第一行为一个正整数 (k),第二行包含 (k) 个整数 (c_1, c_2, ... , c_k)。
输出格式
输出一个整数,即方案总数模 (10^9 + 7) 的结果。
数据规模与约定
- 对于 (50\%) 的数据,(kle 5),(c_ile 3);
- 对于 (100\%) 的数据,(1le kle 15),(1le c_ile 5)。
分析
给定 (c_i),(n=sum c_i),要求长为 (n) 的数列中,数 (i) 出现 (c_i) 次,且相邻两数不同,求满足条件的数列数 (mod{10^9 + 7})。
(50 exttt{pts})
考虑爆搜,每一次枚举下一个字符,卡常可 (40sim 50)。
(50 exttt{pts}) 提交:https://www.luogu.com.cn/record/51716210。
(100 exttt{pts})
考虑记忆化。
注意到每种数出现的次数很少,而数的种类数较多,且数的种类并非本质不同,考虑如下状态:
(large{f_{a,b,c,d,e,last}}):当有 (a) 种数剩下 (1) 个,(b) 种数剩下 (2) 个,……,(e) 种数剩下 (5) 个,且上一个数在加入前有 (last) 个时的方案数。
为方便讲述,这里用 (S_i) 表示剩下 (i) 个数的数构成的集合。易得 (|S_1|=a),(|S_2|=b),……,(|S_5|=e)。
显然,在转移时,有一种数要从 (S_i) 移到 (S_{i-1})。我们可以枚举 (i),而共有 (|S_i|) 种数供选择。现在的问题是,如何保证前后不重复?
其实也很简单。假设数 (v) 从 (S_{last}) 转移到了 (S_{last-1}),那么在枚举 S_{last-1} 的时候,就需要将集合大小 (-1),因为有且仅有其中的 (v) 不满足条件。
综上,转移方程为:
初始 (f_{0,0,0,0,0,i}=1),最后求 (f_{a,b,c,d,e,0})。
在转移时,进行了 (5) 次访问,复杂度为 (mathcal{O}(5sumlimits_{i=1}^{k} inom{n+4}{4})sim Theta( ext{能过}))。
Code
#include <iostream>
#include <cstring>
using namespace std;
typedef long long ll;
constexpr int max_k = 15 + 1, mod = int(1e9) + 7;
ll dp[max_k][max_k][max_k][max_k][max_k][5]; // 记忆化
ll dfs(int a, int b, int c, int d, int e, int lst) // 搜索
{
if (dp[a][b][c][d][e][lst] != -1) // 如果有就直接返回了
return dp[a][b][c][d][e][lst];
int ret = 0;
if (a) ret = (ret + (a - (lst==1)) * dfs(a-1, b, c, d, e, 0))% mod; // 分别转移
if (b) ret = (ret + (b - (lst==2)) * dfs(a+1, b-1, c, d, e, 1))% mod;
if (c) ret = (ret + (c - (lst==3)) * dfs(a, b+1, c-1, d, e, 2))% mod;
if (d) ret = (ret + (d - (lst==4)) * dfs(a, b, c+1, d-1, e, 3))% mod;
if (e) ret = (ret + e * dfs(a, b, c, d+1, e-1, 4))% mod; // 由于没有 last=5 的情况就直接不写了
return dp[a][b][c][d][e][lst] = ret; // 赋值并返回
}
int main()
{
memset(dp, -1, sizeof dp); // 初始化
ios_base::sync_with_stdio(false);
cin.tie(0);
int n, c[5] = {};
cin >> n;
for (int i = 0, ta; i < n; i++) // 统计个数
{
cin >> ta;
c[ta-1]++;
}
for (int i = 0; i < 5; i++) // 初始化
dp[0][0][0][0][0][i] = 1;
cout << dfs(c[0], c[1], c[2], c[3], c[4], 0) << endl; // 计算、输出
return 0; // 然后就 AC 了、
}