「atcoder - ARC089F」ColoringBalls


description

给定 \(N\) 个白球排成一行,再给定长度为 \(K\) 的仅由 'r', 'b' 组成的字符串 \(s\),分别表示红色与蓝色。

执行 \(K\) 次染色操作,第 \(i\) 次任意选择区间 \([L, R]\)(可以为空)并染上 \(s_i\) 所对应的颜色,新颜色将会覆盖原颜色。
额外限制:不能将白色直接涂成蓝色,即蓝色只能涂在红球上。

求最终可以得到序列的不同种类数。

problem link。

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(虽然好像跑得没整数分拆快)

posted @ 2020-07-03 20:19  Tiw_Air_OAO  阅读(22)  评论(0编辑  收藏