「atcoder - ARC089F」ColoringBalls
description
给定 \(N\) 个白球排成一行,再给定长度为 \(K\) 的仅由 'r', 'b' 组成的字符串 \(s\),分别表示红色与蓝色。
执行 \(K\) 次染色操作,第 \(i\) 次任意选择区间 \([L, R]\)(可以为空)并染上 \(s_i\) 所对应的颜色,新颜色将会覆盖原颜色。
额外限制:不能将白色直接涂成蓝色,即蓝色只能涂在红球上。
求最终可以得到序列的不同种类数。
solution
考虑最终序列的结构:它被白色段分割成了若干有色段,每个有色段的涂色是独立的(不存在操作跨越两个有色段)。
那么考虑得到某个有色段所需的有效操作。手玩一下发现:
(1)如果全为红色,则只需要一次 'r' 操作。
(2)否则,至少需要一次 'r' 操作 + 一次 'b' 操作。
在这种情况下,舍去首尾可能有的红色,此时再加入任意 (连续同色段的个数 - 1) / 2 次操作即可构造出这一有色段。
因此,一个最终序列是否合法只与每个有色段所需有效次数有关。
我们用状态 \(\{s_i\}\) 表示一种序列,含义为 “有效次数为 \(i\) 的有色段有 \(s_i\) 个”。
状态数量粗略估计为整数分拆,可以剪枝剪掉一些,最后合法的约有 \(4.2\times 10^5\)。
接下来只需要判状态是否可以被构造,与求每种状态对应最终序列的数量即可。
考虑怎么判某个状态合法。显然先匹配 'r' + 'b' 再匹配 'r'。
匹配 'r' + 'b' 可以从前往后贪心地定位前两个字符 'r' + 'b'。然后从后往前贪心地分配:越靠后的应取有效次数越少的同色段;尽量先分配 'b' 再分配 'r'。
最后判剩下的 'r' 个数是否足够即可。
考虑一个状态对应的方案数怎么算。首先对 \(\{s_i\}\) 求个可重排,表示有色段的位置顺序。
考虑同色段的长度,有些同色段必须存在(长度 > 0),有些同色段可有可无(长度 ≥ 0)。
因此方案数等于 \(\sum x_i = n\) 的解数,其中某些 \(x_i>0\),某些 \(x_i\geq 0\)。经典组合问题。
code
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
#define rep(i, x, n) for(int i=x;i<=n;i++)
#define pr make_pair
#define fi first
#define se second
const int MOD = int(1E9) + 7;
const int MAX = 420000;
inline int add(int x, int y) {x += y; return x >= MOD ? x - MOD : x;}
inline int sub(int x, int y) {x -= y; return x < 0 ? x + MOD : x;}
inline int mul(int x, int y) {return (int) (1LL * x * y % MOD);}
int mpow(int b, int p) {
int r; for(r=1;p;p>>=1,b=mul(b,b))
if( p & 1 ) r = mul(r, b);
return r;
}
struct node{int x[40], cnt;}st[MAX + 5];
int l[75], tot, N, K;
void dfs(int s, node a, int x) {
st[++tot] = a;
for(int i=x;i<=N&&l[i]<=s;i++)
a.x[a.cnt++] = i, dfs(s - l[i], a, i), a.x[--a.cnt] = i;
}
int f[MAX + 5], fct[505], ifct[505], c[505][505];
void init() {
rep(i, 0, 500) rep(j, 0, i)
c[i][j] = (j == 0 ? 1 : add(c[i - 1][j], c[i - 1][j - 1]));
fct[0] = 1; rep(i, 1, 500) fct[i] = mul(fct[i - 1], i);
ifct[500] = mpow(fct[500], MOD - 2);
for(int i=499;i>=0;i--) ifct[i] = mul(ifct[i + 1], i + 1);
l[1] = 2; rep(i, 2, N) l[i] = (i - 1) << 1; dfs(N, (node){}, 1);
rep(i, 1, tot) {
int a = 1, b = 0, t = 1, p = 1;
rep(j, 1, st[i].cnt - 1) {
if( st[i].x[j] != st[i].x[j-1] )
t = mul(t, ifct[p]), p = 1;
else p++;
}
t = mul(t, ifct[p]);
rep(j, 0, st[i].cnt - 1) {
b += l[st[i].x[j]];
if( st[i].x[j] != 1 ) a += 2;
}
f[i] = mul(mul(fct[st[i].cnt], t), c[N + a - 1][a + b - 1]);
}
// printf("%d\n", tot);
}
int tg[75], q[75]; char s[75];
int main() {
scanf("%d%d%s", &N, &K, s + 1), N++, init();
int ans = 0;
rep(i, 1, tot) {
int cnt1 = 0, pos = -1;
rep(j, 0, st[i].cnt - 1) {
if( st[i].x[j] != 1 ) {
pos = j;
break;
} else cnt1++;
}
int hd = 1, tl = 0, p = st[i].cnt - cnt1;
rep(j, 1, K) tg[j] = -1;
for(int j=1;p&&j<=K;j++) {
if( s[j] == 'r' ) q[++tl] = j;
else if( hd <= tl ) tg[q[hd++]] = 0, tg[j] = 1, p--;
}
int cnt[2] = {}; bool flag = true;
for(int j=K;j>=1;j--) {
if( tg[j] == -1 ) cnt[s[j] == 'r' ? 0 : 1]++;
else if( tg[j] == 1 ) {
int tmp = st[i].x[pos] - 2;
while( cnt[1] && tmp ) cnt[1]--, tmp--;
while( cnt[0] && tmp ) cnt[0]--, tmp--;
if( tmp ) {
flag = false;
break;
}
pos++;
}
}
if( flag && cnt[0] >= cnt1 && p == 0 ) ans = add(ans, f[i]);
}
printf("%d\n", ans);
}
details
竟然卡在怎么求方案数。。。降智严重。。。
另外,本题是存在多项式解法的,详见xyx神仙的blog(虽然好像跑得没整数分拆快)。