神仙题。
在下文中,方便起见,用 R/B
表示颜色序列中球的颜色,用 r/b
表示染色序列中将连续的区间染成的颜色。
首先碰到这一类计算有多少个可以被得到的序列的问题,我们为了避免计算重复,肯定会先考虑什么样的序列能够被得到,从而设计一个 DP 状态。此题也不例外。考虑对于一个颜色序列它能否能够被得到,显然对于这个颜色序列中白色格子,它们肯定从来没有被覆盖,因此我们考虑这些白色格子被分割而成的连续段。我们将这些连续段分为两类:不含蓝色格子和含蓝色格子。对于前者,显然一个 r
即可搞定。后者稍微有点复杂,我们来分析下什么样的序列能够染出它,显然同色连续段可以缩在一起,而如果开头是蓝色格子,那我们肯定需要先在它上面染一遍 r
,因此我们不妨在它前面补一个红色格子,对于结尾的格子也同理,这样我们只用考虑 RBRBRB…BR
能够被什么样的染色序列得到即可。手玩一下 RBRBRBR
的染色方案可以发现,RBRBRBR
能够被以下四个长度为 \(4\) 的序列得到:
rbrr
rbrb
rbbr
rbbb
我们可以很明显地发现,能够得到 RBRBRBR
的染色序列的头两个元素都是 rb
,而后两个元素是什么都无所谓,并且显然没有比这个长度更短的序列了,而对于更长的序列,它们肯定包含子序列 rb
,并且 rb
以后都至少有 \(2\) 个元素,因此它们肯定是没有这四个长度为 \(4\) 的序列来得更优的,因此我们不妨归纳一下:对于有 \(B\) 个蓝色连续段的杂色序列,有用的序列长度都是 \(B+1\),并且头两个元素都是 rb
,至于后面的元素,则无所谓。
因此我们假设分出来的杂色连续段有 \(x\) 个,它们当中蓝色格子分别有 \(c_1,c_2,\cdots,c_x\) 个,而只含红色的连续段有 \(y\) 个,那么这样的染色序列能够被染出来当且仅当 \(S\) 能够被拆分成 \(x+y\) 个子序列(不必全部字符都属于某个子序列,但每个字符最多属于一个子序列),其中 \(y\) 个恰好是 r
,而对于另外 \(x\) 个,都以 rb
开头,并且第 \(i\) 个长度恰为 \(c_i+1\)。
这样我们可以贪心地判定一个序列是否可以被染出:我们先贪心地选出最靠前的 \(x\) 个 rb
,再从剩下的子序列中贪心地选出 \(y\) 个 r
,然后我们对于此时的局面,对每个 \(i\) 求出 \([i,n]\) 中有多少个格子目前没有被染色,设为 \(sum_i\),然后我们将选出的 \(x\) 个 rb
按 b
的位置从小到大排序,设为 \(ed_1,ed_2,\cdots,ed_x\),再将 \(c\) 数组从大到小排序,即不妨假设 \(c_i\ge c_{i+1}\),这样我们就贪心地将第一个 rb
分给 \(c\) 最大的连续段,第二个 rb
分给 \(c\) 第二大的,以此类推,这样我们可知一段序列符合要求,当且仅当 \(\forall i,sum_{ed_i}\ge\sum\limits_{j=i}^x(c_j-1)\)。
我们考虑枚举 \(x,y\),然后按照上面的方式先贪心地选出 \(x\) 个 rb
和 \(y\) 个 r
,这样我们可以知道 \(sum_i\) 及 \(ed_i\)。然后我们考虑倒着 DP,设 \(dp_{i,j,k}\) 表示目前钦定了 \(i\sim x\) 的段中蓝色点的个数,目前 \(\sum\limits_{j=i}^xc_j-1=j,c_i=k\),有多少种杂色段之间两两之间位置关系的可能,转移就枚举 \(c_j=k\) 的 \(j\) 有多少个,设为 \(p\),那么转移就类似于一个二项加法卷积,即 \(dp_{i,j,k}=\sum\limits_{p}\sum\limits_{l}dp_{i+p,j-(k-1)p,l}·\dbinom{x-i+1}{p}[l<k]\),注意到 \(p\) 的枚举是调和级数,因此 \(p\) 枚举的总复杂度是 \(\ln n\) 级别的,而 \(l\) 的枚举可以前缀和优化掉,这样单次 DP 就达到了 \(n^3\ln n\)。
接下来考虑怎样计算一次 DP 对答案的贡献,即如何用 \(dp_{1,j,k}\) 即 \(x,y\) 的值计算答案,首先段之间的位置关系,\(x\) 个杂色段之间的位置关系已经在 DP 过程中钦定了,而 \(y\) 个杂色段与 \(x\) 个杂色段之间的位置关系就做个组合数,即 \(\dbinom{x+y}{x}\),接下来考虑怎样计算每个连续段中球的数量有多少种可能,我们先假设每个连续段为一个“盒子”,每个盒子中必须装同一颜色的球,那么先不妨假设对于一个由 \(c\) 个 B 构成的杂色连续段,它只有 BRBRBRBR…B
,那么这样有 \(2c-1\) 个盒子,每个盒子中都必须至少有一个球,而两边还可以放一些 R,因此还会产生两个可空的盒子,对于每个纯色连续段,它肯定恰好会产生一个必须放球的盒子,而连续段与连续段之间的 \(x+y-1\) 个空隙,它们必须放黑球,有 \(x+y-1\) 个必须放球的盒子,而两边的黑球盒子可以空,因此还有两个可以为空的盒子,这样必须放球的盒子数 \(A=2j+2x+2y-1\),可以为空的盒子数 \(B=2x+2\),组合数算贡献即可。
时间复杂度 \(n^5\ln n\),由于常数很小,可以通过。
启示:
对于求解有多少个序列能够被得到的这一类问题,不妨先考虑一下什么样的序列能够被得到,而如果没有推出足够的性质(比如说如果每次操作的啥啥啥不同,最终得到的序列一定不同)之前,最好不要直接从操作序列的时间轴入手 DP。
如果一些信息在 DP 的过程中不能直接求得/计算 DP 的系数时需要用到这些信息,那不妨先枚举它们,这样 DP 的时候系数就比较好求了。
P.S.:如果你下面程序的最后一组数据输出 2088,看看是不是多算了 BRB黑BRB
这一不合法的序列。
#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define fill0(a) memset(a, 0, sizeof(a))
#define fill1(a) memset(a, -1, sizeof(a))
#define fillbig(a) memset(a, 63, sizeof(a))
#define pb push_back
#define ppb pop_back
#define mp make_pair
#define mt make_tuple
#define eprintf(...) fprintf(stderr, __VA_ARGS__)
template <typename T1, typename T2> void chkmin(T1 &x, T2 y){
if (x > y) x = y;
}
template <typename T1, typename T2> void chkmax(T1 &x, T2 y){
if (x < y) x = y;
}
typedef pair<int, int> pii;
typedef long long ll;
typedef unsigned int u32;
typedef unsigned long long u64;
typedef long double ld;
namespace fastio {
#define FILE_SIZE 1 << 23
char rbuf[FILE_SIZE], *p1 = rbuf, *p2 = rbuf, wbuf[FILE_SIZE], *p3 = wbuf;
inline char getc() {
return p1 == p2 && (p2 = (p1 = rbuf) + fread(rbuf, 1, FILE_SIZE, stdin), p1 == p2) ? -1: *p1++;
}
inline void putc(char x) {(*p3++ = x);}
template <typename T> void read(T &x) {
x = 0; char c = getchar(); T neg = 0;
while (!isdigit(c)) neg |= !(c ^ '-'), c = getchar();
while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
if (neg) x = (~x) + 1;
}
template <typename T> void recursive_print(T x) {
if (!x) return;
recursive_print (x / 10);
putc (x % 10 ^ 48);
}
template <typename T> void print(T x) {
if (!x) putc('0');
if (x<0) putc('-'), x = -x;
recursive_print(x);
}
template <typename T> void print(T x,char c) {print(x); putc(c);}
void print_final() {fwrite(wbuf, 1, p3-wbuf, stdout);}
}
const int MAXN = 70;
const int MAXC = 500;
const int MOD = 1e9 + 7;
int n, k, c[MAXC + 5][MAXC + 5]; char s[MAXN + 5];
bool vis[MAXN + 5]; int ed[MAXN + 5], suf[MAXN + 5], sum[MAXN + 5];
bool contribute(int x, int y) {
memset(vis, 0, sizeof(vis));
int c1 = 0, c2 = 0;
for (int i = 1; i <= k; i++) {
if (s[i] == 'r') {
if (c1 < x + y) c1++, vis[i] = 1;
} else {
if (c2 < x && c1 > c2) c2++, vis[i] = 1, ed[c2] = i;
}
}
if (c1 != x + y || c2 != x) return 0;
suf[k + 1] = 0;
for (int i = k; i; i--) suf[i] = suf[i + 1] + (!vis[i]);
memset(sum, 0, sizeof(sum));
for (int i = 1; i <= x; i++) sum[i] = suf[ed[i]];
return 1;
}
int dp[MAXN + 5][MAXN + 5][MAXN + 5], sdp[MAXN + 5][MAXN + 5][MAXN + 5];
void calc_dp(int x, int y) {
memset(sdp, 0, sizeof(sdp)); memset(dp, 0, sizeof(dp));
dp[x + 1][0][0] = 1;
for (int i = 0; i <= n; i++) sdp[x + 1][0][i] = 1;
for (int i = x; i; i--) {
for (int j = 0; j <= sum[i]; j++) {
for (int l = 1; l <= n; l++) {
for (int p = 1; p * (l - 1) <= j && p <= x - i + 1 && j - (l - 1) * p <= sum[i + p]; p++) {
dp[i][j][l] = (dp[i][j][l] + 1ll * c[x - i + 1][p] * sdp[i + p][j - (l - 1) * p][l - 1]) % MOD;
}
sdp[i][j][l] = (sdp[i][j][l - 1] + dp[i][j][l]) % MOD;
}
}
}
}
int main() {
// freopen("color.in", "r", stdin);
// freopen("color.out", "w", stdout);
scanf("%d%d%s", &n, &k, s + 1);
for (int i = 0; i <= MAXC; i++) {
c[i][0] = 1;
for (int j = 1; j <= i; j++) {
c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % MOD;
}
}
int res = 0;
for (int x = 0; x <= n; x++) {// x sequences with at least 1 blue ball
for (int y = 0; x + y <= n; y++) {// y sequences with only red balls
if (!x && !y) continue;
if (!contribute(x, y)) continue;
// printf("x = %d; y = %d:\n", x, y);
// for (int i = 1; i <= k; i++) printf("%d ", vis[i]);
// printf("\n");
// for (int i = 1; i <= x; i++) printf("%d ", sum[i]);
// printf("\n");
calc_dp(x, y);
for (int s = 0; s <= sum[1]; s++) {
for (int lst = 0; lst <= n; lst++) if (dp[1][s][lst]) {
int A = (2 * s + x) + (x + y - 1) + y, B = 2 * x + 2;
// printf("!! %d %d %d %d %d %d\n", s, lst, dp[1][s][lst], A, B,
// 1ll * dp[1][s][lst] * c[x + y][y] % MOD * c[n + B - 1][A + B - 1] % MOD);
res = (res + 1ll * dp[1][s][lst] * c[x + y][y] % MOD * c[n + B - 1][A + B - 1]) % MOD;
}
}
}
}
printf("%d\n", (res + 1) % MOD);
return 0;
}
/*
4 3
rrb
7 4
rbrb
7 6
rbrrrb
*/
手机扫一扫
移动阅读更方便
你可能感兴趣的文章