「CF1831E」Hyperregular Bracket Strings 题解

发布时间 2023-07-19 17:37:05作者: zsc985246

本文网址:https://www.cnblogs.com/zsc985246/p/17565768.html ,转载请注明出处。

前言

没见过的套路,写篇题解记录一下。

题目大意

给定 \(n\)\(k\) 个区间 \([l_i,r_i]\),你需要找出满足以下条件的合法括号序列个数:

  • 序列的长度为 \(n\)

  • \(\forall i \in [1,k]\),区间 \([l_i,r_i]\) 的括号序列合法。

答案对 \(998244353\) 取模。

\(1 \le n \le 3 \times 10^5,0 \le k \le 3 \times 10^5,1 \le l_i \le r_i \le n\)

思路

看到括号序列,直接把 ( 看成 \(+1\)) 看成 \(-1\)

我们知道,对于一个长度为 \(n\) 的合法括号序列,令它的前缀和为 \(s\),那么 \(s_0 = s_n = 0,\forall i \in [1,n-1],s_i \ge 0\)

那么我们就将题目转化为如下:

求由 \(+1\)\(-1\) 组成且满足 \(k\) 条限制的长度为 \(n\) 的合法序列 \(s\) 个数,其中第 \(i\) 条限制为区间 \([l_i,r_i]\) 合法。

一个区间 \([l,r]\) 合法当且仅当 \(s_{l-1}=s_r,\forall i \in [l,r-1],s_i \ge s_r\)

首先如果 \([l_i,r_i]\)\([l_j,r_j]\) 不相交,可以分开计算答案,所以我们只需要处理相交的情况

如果 \(l_i \le l_j \le r_j \le r_i\),即两个区间为包含关系,发现区间 \([l_i,l_j)\)\((r_j,r_i]\) 的括号序列拼接起来一定合法。

如果 \(l_i \le l_j \le r_i \le r_j\),即两个区间不互相包含,根据上方的结论,\(s_{l_j} \ge s_{l_i} = s_{r_i},s_{r_i} \ge s_{l_j} = s_{r_j}\),可以推出 \(s_{l_i}=s_{r_i}=s_{l_j}=s_{r_j}\),也就可以推出区间 \([l_j,r_i]\) 也必须合法。

这个时候我们就可以使用套路。我们给每个区间随机分配一个权值,一个位置的权值就是包含这个位置的区间的权值异或和

这可以使用前缀异或和维护。这样做的好处是所有权值相同的位置拿出来一定构成合法序列,并且每个位置恰好属于一个合法序列。

括号序列有一个结论:长度为 \(n\) 的合法括号序列的方案数是卡特兰数第 \(\frac{n}{2}\) 项。对每个权值都算一次卡特兰数,相乘即为答案。

代码实现

不建议使用 rand 和 mt19937,因为值域不够大,容易冲突。除了使用代码中的 mt19937_64 之外,还可以使用 hash。

#include<bits/stdc++.h> 
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
const ll N=1e6+10;
using namespace std;
const ll p=998244353;
//组合数模板
ll ksm(ll a,ll b){ll bns=1;while(b){if(b&1)bns=bns*a%p;a=a*a%p;b>>=1;}return bns;}
ll jc[N],inv[N];
void init(ll n){jc[0]=1;For(i,1,n)jc[i]=jc[i-1]*i%p;inv[n]=ksm(jc[n],p-2);Rep(i,n-1,0)inv[i]=inv[i+1]*(i+1)%p;}
ll C(ll n,ll m){if(m<0)return 1;if(n<m)return 0;return jc[n]*inv[m]%p*inv[n-m]%p;}
//随机数初始化
mt19937_64 rnd(random_device{}());
uniform_int_distribution<ll>dist(0,LLONG_MAX);

ll n,m,k;
ll a[N];

ll Catalan(ll x){//卡特兰数第x项
	return (C(2*x,x)-C(2*x,x-1)+p)%p;
}

void mian(){
	
	scanf("%lld%lld",&n,&k);
	For(i,0,n)a[i]=0;
	For(i,1,k){
		ll l,r;
		scanf("%lld%lld",&l,&r);
		//分配随机权值
		ll t=dist(rnd);
		a[l]^=t,a[r+1]^=t;
	}
	map<ll,ll>vis;
	For(i,1,n)vis[a[i]^=a[i-1]]++;//统计各个权值的数量
	ll ans=1;
	for(auto i:vis){
		if(i.second&1)ans=0;//不合法
		else ans=ans*Catalan(i.second/2)%p;//计算答案
	}
	printf("%lld\n",ans);
	
}

int main(){
	init(1e6);//组合数预处理
	int T=1;
	scanf("%d",&T);
	while(T--)mian();
	return 0;
}

尾声

如果你发现了问题,你可以直接回复这篇题解

如果你有更好的想法,也可以直接回复!