Atcoder 题面传送门 & 洛谷题面传送门

神仙题。

在下文中,方便起见,用 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
*/

Atcoder Regular Contest 089 D - ColoringBalls(DP)的更多相关文章

  1. Atcoder Grand Contest 033 D - Complexity(dp)

    Atcoder 题面传送门 & 洛谷题面传送门 首先 \(n^5\) 的暴力非常容易想,设 \(dp_{a,b,c,d}\) 表示以 \((a,b)\) 为左上角,\((c,d)\) 为右下角 ...

  2. AtCoder Grand Contest 031 B - Reversi(DP)

    B - Reversi 题目链接:https://atcoder.jp/contests/agc031/tasks/agc031_b 题意: 给出n个数,然后现在你可以对一段区间修改成相同的值,前提是 ...

  3. Atcoder Regular Contst 084 D - XorShift(bitset)

    洛谷题面传送门 & Atcoder 题面传送门 没错,这就是 Small Multiple 那场的 F,显然这种思维题对我来说都是不可做题/cg/cg/cg 首先如果我们把每个二进制数看作一个 ...

  4. Atcoder Grand Contest 021 F - Trinity(dp+NTT)

    Atcoder 题面传送门 & 洛谷题面传送门 首先我们考虑设 \(dp_{i,j}\) 表示对于一个 \(i\times j\) 的网格,其每行都至少有一个黑格的合法的三元组 \((A,B, ...

  5. Atcoder Regular Contest 125 E - Snack(最小割转化+贪心)

    Preface: 这是生平第一道现场 AC 的 arc E,也生平第一次经历了 performance \(\ge 2800\)​,甚至还生平第一次被 hb 拉到会议里讲题,讲的就是这个题,然鹅比较尬 ...

  6. AtCoder Regular Contest 089

    这场一边吃饭一边打,确实还是很菜的 C - Traveling Time limit : 2sec / Memory limit : 256MB Score : 300 points Problem ...

  7. AtCoder Grand Contest 032-B - Balanced Neighbors (构造)

    Time Limit: 2 sec / Memory Limit: 1024 MB Score : 700700 points Problem Statement You are given an i ...

  8. 2019 Multi-University Training Contest 1 A.Blank(dp)

    题意:现在要你构造一个只有{0,1,2,3} 长度为n且有m个限制条件的序列 问你方案数 思路:dp[i][j][k][now]分别表示四个数最后出现的位置 最后可以滚动数组 优化一下空间 ps:我的 ...

  9. AtCoder Regular Contest 093

    AtCoder Regular Contest 093 C - Traveling Plan 题意: 给定n个点,求出删去i号点时,按顺序从起点到一号点走到n号点最后回到起点所走的路程是多少. \(n ...

随机推荐

  1. NXOpen.BlockStyler的一些用法

    关于BLOCK UI的一些控件的用法,本人曾经使用的代码,拿出来共享: Option Strict Off Imports NXOpen Imports NXOpen.BlockStyler Impo ...

  2. 初学python-day9 函数1(已更新)

    函数 一.函数基础 1.什么是函数 在一个完整的项目中,某些功能会被重复使用,那么会将代码段封装成函数,当我们要使用的时候,直接调用即可. 函数是可以实现一定的小程序或者功能. 优点: 增加了代码的重 ...

  3. Less-(1~4) union select

    Less-1: 核心语句: 无任何防护:回显查询结果或错误内容. 输入单引号闭合语句中的单引号,#注释后面的内容,即可注入.由于有查询结果回显,直接联合注入即可. 1'order by x #(有些环 ...

  4. 【UE4 调试】C++ 几种编译方法和小技巧

    编译方法 Visual Studio 2019 编译 默认编译 UnrealVS 快速编译 Editor 编译 一般 vs 编译完后,Editor会跟着热编译(有声音) 如果发现编译后代码没更新到Ed ...

  5. Java RMI学习与解读(一)

    Java RMI学习与解读(一) 写在前面 本文记录在心情美丽的一个晚上. 嗯.就是心情很美丽. 那为什么晚上还要学习呢? emm... 卷... 卷起来. 全文基本都是根据su18师傅和其他师傅的文 ...

  6. UltraSoft - Beta - Postmortem事后分析

    UltraSoft - Beta - PostMORTEM 设想和目标 我们的软件要解决什么问题?是否定义得很清楚?是否对典型用户和典型场景有清晰的描述? 解决的问题和定义都在[软软软]功能规格说明书 ...

  7. 零基础入门Linux有什么好的学习方法吗?(超详细)

    本节旨在介绍对于初学者如何学习 Linux 的建议,在这里不具体分析Linux的学习节点只分析对于零基础的伙伴的学习方法.那么如果你已经确定对 Linux 产生了兴趣,那么接下来我们介绍一下学习 Li ...

  8. RocketMQ源码详解 | Broker篇 · 其一:线程模型与接收链路

    概述 在上一节 RocketMQ源码详解 | Producer篇 · 其二:消息组成.发送链路 中,我们终于将消息发送出了 Producer,在短暂的 tcp 握手后,很快它就会进入目的 Broker ...

  9. 访问单个结点的删除 牛客网 程序员面试金典 C++ Python

    访问单个结点的删除 牛客网 程序员面试金典 C++ Python 题目描述 实现一个算法,删除单向链表中间的某个结点,假定你只能访问该结点. 给定待删除的节点,请执行删除操作,若该节点为尾节点,返回f ...

  10. hdu 2860 Regroup(并查集)

    题意: AP x yA recruit with ability rate x were asked to join company y. (0<=x<2^31, 0<=y<n ...