「CF1831E」Hyperregular Bracket Strings 题解
本文网址:https://www.cnblogs.com/zsc985246/p/17565768.html ,转载请注明出处。
前言
没见过的套路,写篇题解记录一下。
传送门
「CF1831E」Hyperregular Bracket Strings
题目大意
给定 \(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-1} \ge s_{l_i-1} = s_{r_i},s_{r_i} \ge s_{l_j-1} = s_{r_j}\),可以推出 \(s_{l_i-1}=s_{r_i}=s_{l_j-1}=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;
}
尾声
如果你发现了问题,你可以直接回复这篇题解
如果你有更好的想法,也可以直接回复!