P7044 「MCOI-03」括号 组合数学 思维

这篇博客探讨了如何解决括号匹配问题,利用组合数学和动态规划计算不同偏值下的括号字符串匹配计数。文章详细介绍了算法思路,通过递归公式和区间划分,最终实现O(n log n)的时间复杂度。内容涵盖了从基础的0级偏值到更高级别的偏值计算,并提供了C++代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

P7044 「MCOI-03」括号

神仙组合数学题

P7044 「MCOI-03」括号

先考虑 0 级偏值如何计算,统计子串中不匹配的括号数即为答案,将其设为 f ( l , r , 0 ) f(l,r,0) f(l,r,0)

对于 f ( 1 , n , 0 ) f(1,n,0) f(1,n,0) 可以对于每个左括号 i i i 找到与其第一个匹配的右括号 j j j ,那么这个左括号的贡献就是 i × ( j − i ) i\times(j-i) i×(ji) O ( N ) O(N) O(N) 得出答案。

考虑对于 k k k 级偏值如何计算:
f ( l , r , 1 ) = ∑ l ′ ⩾ l r ∑ r ′ ⩾ l ′ r f ( l ′ , r ′ , 0 ) f ( l , r , 2 ) = ∑ l ′ ⩾ l r ∑ r ′ ⩾ l ′ r ( ∑ l ′ ′ ⩾ l ′ r ′ ∑ r ′ ⩾ l ′ ′ r ′ f ( l ′ ′ , r ′ ′ , 0 ) ) f ( l , r , k ) = ∑ l ′ ⩾ l r ∑ r ′ ⩾ l ′ r ( ∑ l ′ ′ ⩾ l ′ r ′ ∑ r ′ ⩾ l ′ ′ r ′ ⋯ ∑ ∑ f ( l t , r t , 0 ) ) ⏟ k 次 ∑ l ∑ r 求 和 \begin{aligned} f(l,r,1) &=\sum_{l'\geqslant l}^{r}\sum_{r'\geqslant l'}^{r}f(l',r',0)\\ f(l,r,2) &= \sum_{l'\geqslant l}^{r}\sum_{r'\geqslant l'}^{r}\left(\sum_{l''\geqslant l'}^{r'}\sum_{r'\geqslant l''}^{r'}f(l'',r'',0)\right)\\ f(l,r,k) &= \underbrace{\sum_{l'\geqslant l}^{r}\sum_{r'\geqslant l'}^{r}\left(\sum_{l''\geqslant l'}^{r'}\sum_{r'\geqslant l''}^{r'}\cdots \sum \sum f(lt,rt,0)\right)}_{k 次 \sum_l\sum_r 求和} \end{aligned} f(l,r,1)f(l,r,2)f(l,r,k)=llrrlrf(l,r,0)=llrrlrllrrlrf(l,r,0)=klr llrrlrllrrlrf(lt,rt,0)
考虑 f ( l t , r t , 0 ) f(lt,rt,0) f(lt,rt,0) 对答案的贡献,通过上式,我们可以发现,每次枚举的 l l l r r r 的范围都在减小,如下图:

在这里插入图片描述

然后就会发现, f ( l t , r t , 0 ) f(lt,rt,0) f(lt,rt,0) 对答案的贡献次数就是

[ l , r ] [l,r] [l,r] 开始,每次缩小范围,令 l ⩽ l ′ ⩽ r ′ ⩽ r l\leqslant l'\leqslant r'\leqslant r llrr ,缩小 k k k 次变为 [ l t , r t ] [lt,rt] [lt,rt] 的方案数 ⇔ \Leftrightarrow [ l , l t ] [l,lt] [l,lt] 中选取 k − 1 k - 1 k1 个单调不降的数且从 [ r t , r ] [rt,r] [rt,r] 中选取 k − 1 k-1 k1 个单调不降的数 ⇔ \Leftrightarrow l t − l lt-l ltl 拆分为 k k k 个自然数且将 r − r t r-rt rrt 拆分为 k k k 个自然数的方案总数(从上图可以看出,其实就是将一段 [ l , l t ] [l,lt] [l,lt] 分成 k k k 段的方案数)

于是答案就变为
f ( l , r , k ) = ∑ l ⩽ l ′ ⩽ r ′ ⩽ r ( l ′ − l + k − 1 k − 1 ) ( r − r ′ + k − 1 k − 1 ) f ( l ′ , r ′ , 0 ) f ( 1 , n , k ) = ∑ 1 ⩽ l ′ ⩽ r ′ ⩽ n ( l ′ + k − 2 k − 1 ) ( n − r ′ + k − 1 k − 1 ) f ( l ′ , r ′ , 0 ) f(l,r,k)=\sum_{l\leqslant l'\leqslant r'\leqslant r}\binom{l'-l+k-1}{k-1}\binom{r-r'+k-1}{k-1}f(l',r',0)\\[2ex] f(1,n,k)=\sum_{1\leqslant l'\leqslant r'\leqslant n}\binom{l'+k-2}{k-1}\binom{n-r'+k-1}{k-1}f(l',r',0) f(l,r,k)=llrr(k1ll+k1)(k1rr+k1)f(l,r,0)f(1,n,k)=1lrn(k1l+k2)(k1nr+k1)f(l,r,0)
枚举递增地右端点 r r r ,答案变为
f ( 1 , n , k ) = ( n − r ′ + k − 1 k − 1 ) ∑ 1 ⩽ l ′ ⩽ r ′ ( l ′ + k − 2 k − 1 ) f ( l ′ , r ′ , 0 ) f(1,n,k)=\binom{n-r'+k-1}{k-1}\sum_{1\leqslant l'\leqslant r'}\binom{l'+k-2}{k-1}f(l',r',0) f(1,n,k)=(k1nr+k1)1lr(k1l+k2)f(l,r,0)
考虑维护
∑ 1 ⩽ l ′ ⩽ r ′ ( l ′ + k − 2 k − 1 ) f ( l ′ , r ′ , 0 ) \sum_{1\leqslant l'\leqslant r'}\binom{l'+k-2}{k-1}f(l',r',0) 1lr(k1l+k2)f(l,r,0)
我们知道,子串的贡献一定是将所有合法括号消去后,剩下未匹配括号的数目,也就是如此形式:))...))(((..(((,那么每当 r r r 向右移一位,就会有 )或者 (读入,此时分类讨论:

  1. )读入,则若当前没有未匹配的左括号,则对 [ 1 , r − 1 ] [1,r-1] [1,r1] 中的 f f f 区间 +1,如果当前有未匹配的左括号则区间 -1.
  2. (读入,则区间 +1.

在这里插入图片描述

于是考虑用线段树维护即可达到 O ( n log ⁡ n ) O(n\log n) O(nlogn) 的复杂度。

当然也可以维护 ( l ′ + k − 2 k − 1 ) \binom{l'+k-2}{k-1} (k1l+k2) 的前缀和或者枚举 l l l ,维护 ( n − r ′ + k − 1 k − 1 ) \binom{n-r'+k-1}{k-1} (k1nr+k1) 的后缀和,然后就能够 O ( n ) O(n) O(n) 解决问题。

#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>

using namespace std;
typedef long long ll;
const ll modn = 998244353;
ll n,k,prod[3000005],inv[3000005],prodinv[3000005];
ll pre[3000005],cnt1,cnt2,ans,tmp,stk[3000005],tmp2;
string s;

ll C(ll n,ll m)
{
	if(n < m) return 0;
	return ((prod[n]%modn*(prodinv[m]%modn))%modn*(prodinv[n - m]%modn))%modn;
}

int main()
{
	scanf("%lld%lld",&n,&k);cin >> s;
	if(k == 0)
	{
		for(int i = 1;i <= n;i ++)
		{
			if(s[i - 1] == '(') cnt1 ++,ans ++;
			else
			{
				if(cnt1 ) cnt1 --,ans --;
				else ans ++;
			}
		}
		printf("%lld",(ans%modn + modn)%modn);
		return 0;
	}
	prod[0] = 1;inv[0] = 1;inv[1] = 1;prodinv[0] = 1;
	for(ll i = 1;i <= n + k + 15;i ++) prod[i] = ((prod[i - 1]%modn) * (i%modn))%modn;
	for(ll i = 2;i <= n + k + 15;i ++) inv[i] = ((modn - modn / i)%modn * (inv[modn % i]%modn))%modn;
	for(ll i = 1;i <= n + k + 15;i ++) prodinv[i] = ((prodinv[i - 1]%modn) * (inv[i]%modn))%modn;
	for(ll i = 1;i <= n + 15;i ++) pre[i] = (pre[i - 1]%modn + C(i+k-(ll)2,k-(ll)1)%modn)%modn;
	tmp = 0;
	for(ll i = 1;i <= n;i ++)
	{
		if(s[i - 1] == '(') tmp = (tmp%modn + pre[i]%modn)%modn ,cnt1 ++,stk[cnt1] = i;
		else
		{
			if(cnt1 > 0)
			{
				tmp2 = stk[cnt1];cnt1 --;//消去括号
				tmp = ((tmp%modn - pre[tmp2]%modn + modn)%modn);//计算改变的值
				tmp = (tmp%modn + (pre[i]%modn - pre[tmp2]%modn + modn)%modn)%modn; 
			}
			else tmp = (tmp%modn + pre[i]%modn)%modn,cnt2 ++;
		}
		ans = ((ans%modn + ((tmp%modn)*(C(n-i+k-(ll)1,k-(ll)1)%modn))%modn)%modn + modn)%modn;//贡献到答案里
	}
	printf("%lld",(ans%modn + modn)%modn);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值