Loading

[题解][ARC089D] ColoringBalls

题目大意

\(n\) 个白色的小球排成一排,有一个长为 \(k\) 的字符串 \(S\)。接下来进行 \(k\) 次操作。

\(i\) 个操作,选择一段连续的小球(可以为空),若 \(S\) 中第 \(i\) 个字符是 r,则将这些球染成红色;若是 b,则将它们染成蓝色。由于染料的特性,不能直接用蓝色来染白色。

求在进行完所有操作后,所有小球的颜色序列可以有多少种。

\(10^9+7\) 取模。

\(n,k\le 70\).

解题思路

首先,因为考虑按顺序进行染色操作的结果非常复杂,而且难以分辨重复状态,所以不妨考虑如何判断一个序列是否合法。

不妨假设整个序列有 \(m\) 个有色段,每段的蓝色段个数为 \(b_i\)

显然对于 \(b_i=0\) 的段,只需要一个 r;而对于 \(b_i>1\) 的段,经过手玩不难发现,它需要一个 rb,以及 \(b_i-1\) 次任意染色。

所以假设我们已经有了 \(b\) 数组,不妨贪心地达成染色方案:

按照 \(b_i\) 的从大到小排序,首先尽量选最靠前的 r,然后对于 \(b_i>0\) 的,在选的 r 之后尽量选最靠前的 b,最后对于 \(b_i>1\) 的,选择 rb 后最靠前的 \(b_i-1\) 个操作。

因为 \(n\le 70\),可以用复杂度划分数相关的做法,那么不妨直接类似划分数枚举 \(b_i\) 的情况。

接下来对于已经确定的一组 \(b_i\) 如何求方案的思路,个人感觉比较妙 (从 xht37 那里学到的)。

\(b={1,2,2,3,4}\) 为例,首先是一个多重组合数 \(\binom{5}{1,2,1,1}\),然后是具体染色的方案数。

这个序列对应的染色序列一定包含子序列 R W B W B W BRB W BRBRB,也就是每个 \(b_i\) 的最短可能,长度为 \(15\)

同时,其对应的染色序列一定是 W R W RBR W RBR W RBRBR W RBRBRBR W 的子序列,也就是每个 \(b_i\) 的最长可能,长度为 \(25\)

而此时我们的染色序列是把相邻的同色球缩到一起,所以最终方案相当于把 \(n-15\) 个字符分成 \(25\) 组,每组可以为空的方案数。答案为 \(\binom{n-15+25-1}{25-1}\)

#include <bits/stdc++.h>
#define lfor(x, y, z) for(int x(y), x##_end(z); x <= x##_end; ++x)
#define rfor(x, y, z) for(int x(y), x##_end(z); x >= x##_end; --x)
#define lep(x, y, z) for(int x(y), x##_end(z); x < x##_end; ++x)
#define ls(x) t[x].lc
#define rs(x) t[x].rc
#define LL long long
using namespace std;

const int N(205), mod(1e9 + 7);

int n, K; char ch[N];
int Ans;
int m, b[N];
int ban[N], loc[N];
int fac[N], ifac[N];
int div_cnt;

template <typename T> inline void read(T &x){
	x = 0; int f = 1, c = getchar();
	while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
	while(isdigit(c)) x = x * 10 + c - '0', c = getchar();
	x *= f;
}

inline int Mod(int x){ return x + ((x >> 31) & mod); }
inline void MOD(int &x){ x = x + ((x >> 31) & mod); }
inline int C(int n, int m){
	if(n < m || m < 0) return 0;
	return (LL) fac[n] * ifac[n - m] % mod * ifac[m] % mod;
}
inline int qpow(int x, int a = mod - 2){
	int sum = 1;
	while(a) sum = a & 1 ? (LL) sum * x % mod : sum, x = (LL) x * x % mod, a >>= 1;
	return sum;
}

void prep(int n = 200){
	fac[0] = 1; lfor(i, 1, n) fac[i] = (LL) fac[i - 1] * i % mod;
	ifac[n] = qpow(fac[n]); rfor(i, n - 1, 0) ifac[i] = (LL) ifac[i + 1] * (i + 1) % mod;
}

bool legal(){
	int now = 1, sum = m - 1;
	
	lfor(i, 1, K) ban[i] = 0;
	lfor(i, 1, m) if(b[i]) sum += b[i] * 2 - 1; if(sum > n) return 0;

	rfor(i, m, 1){
		while(now <= K && ch[now] != 'r') ++now;
		if(now > K || ch[now] != 'r') return 0; loc[i] = now, ban[now] = 1, ++now;
	}
	
	now = 1;
	rfor(i, m, 1) if(b[i]){
		now = max(now, loc[i] + 1);
		while(now <= K && ch[now] != 'b') ++now;
		if(now > K || ch[now] != 'b') return 0; loc[i] = now, ban[now] = 1, ++now;
	}

	now = 1;
	rfor(i, m, 1) if(b[i]){
		int sum = b[i] - 1;
		now = max(now, loc[i] + 1);
		while(now <= K && sum) sum -= !ban[now], ++now;
		if(sum) return 0;
	}
	return 1;
} 

inline int Calc(){
	int sum = fac[m], p = 1;
	lfor(i, 2, m) if(b[i] != b[p]) sum = (LL) sum * ifac[i - p] % mod, p = i;
	return (LL) sum * ifac[m - p + 1] % mod;
}
void check(){
	if(!legal()) return;
	int A = m - 1, B = m + 1;
	lfor(i, 1, m){
		if(b[i] == 0) A += 1, B += 1;
		else A += b[i] * 2 - 1, B += b[i] * 2 + 1;
	}
	int tmp = (LL) Calc() * C(n - A + B - 1, B - 1) % mod;
	MOD(Ans += (LL) Calc() * C(n - A + B - 1, B - 1) % mod - mod);
}
void dfs(int R, int A, int L, int lst){
	check(); 
	++m;
	for(int i = lst; R && i + 1 <= A && L + max(1, i * 2 - 1) <= n + 1; ++i)
		b[m] = i, dfs(R - 1, A - 1 - i, L + max(1, i * 2 - 1), i);
	--m;
}

int main(){
	read(n), read(K), prep();
	scanf("%s", ch + 1);
	
	int cntr = 0, cntb = 0;
	lfor(i, 1, K) if(ch[i] == 'r') ++cntr; else ++cntb;
	
	dfs(cntr, K + 1, 0, 0);
	
	cout << Ans << endl;
	return 0;
}
posted @ 2022-04-12 10:30  IrisT  阅读(28)  评论(0编辑  收藏  举报