题目描述
难度分:2400
输入T(≤105)表示T组数据。所有数据的n之和≤3×105,k之和≤3×105。
每组数据输入n(1≤n≤3×105),k(0≤k≤3×105)和k个闭区间 [li,ri],区间端点均在[1,n]内。
输出有多少个长为n的合法括号字符串s,满足s的每个子串[li,ri]也是合法括号字符串。
考虑到答案可能很大,对其模998244353后再输出。
输入样例
7
6 0
5 0
8 1
1 3
10 2
3 4
6 9
1000 3
100 701
200 801
300 901
28 5
1 12
3 20
11 14
4 9
18 19
4 3
1 4
1 4
1 4
输出样例
5
0
0
4
839415253
140
2
算法
差分数组+组合数学
今天这道题的随机异或技巧我真是第一次见,首先有个前置结论:长度为2n的括号序列有Cn2n个(即卡特兰数)。下面分类讨论一下:
-
如果k=1,那么区间[l1,r1]为合法括号序列的方案有a=C[r1−l1+12]r1−l1+1种,其中[.]表示对.向下取整。而剩下的部分拼起来也必须要是合法的括号序列,假设剩下的长度为len=n−(r1−l1+1),答案就是b=C[len2]len,最终答案就是a×b。
-
如果k>1,如法炮制,但是需要注意如果两个区间[l1,r1]和[l2,r2]相交,比如[l1,r1]∈[l2,r2],约束就会被分成三段[l2,l1]、[l1,r1]、[r1,r2]。需要分别计算三段的卡特兰数,三者乘起来才是整体的答案。再比如,若l2∈[l1,r1]且r2>r1,约束就会被分为[l1,l2]、[l2,r1]、[r1,r2]三段。
所以我们遍历所有区间[li,ri],只要能确定最终被分裂成了多少个区间,就能确定最终的方案数。问题就在于如何确定哪些区间是可以拼在一起考虑的,对于某个区间[li,ri],可以在这个区间上异或上一个随机数,操作差分数组d就只需要操作li和ri+1两个端点。遍历完所有区间后求d的前缀异或和,就能得到结果,异或值相同的位置就可以连接起来形成一个合法的括号串。
接下来用哈希表对[1,n]上的异或值分组,异或值为eori的位置个数cnti就是该值拼出来的合法括号长度。把所有的C[cnti2]cnti乘起来就是答案。
复杂度分析
时间复杂度
遍历每个区间[li,ri]时对差分数组d上异或一个数的时间复杂度为O(1),因此k个区间的时间复杂度为O(k)。接下来对d求前缀和,并得到哈希表分组结果的时间复杂度为O(n)。哈希表在最差情况下有O(n)个键值对(几乎每个位置的异或值都不同),所以遍历哈希表计算最终答案的时间复杂度也为O(n)。算法整体的时间复杂度为O(k+n)。
空间复杂度
空间消耗在于哈希表和差分数组d,它们消耗的空间都是线性的,因此算法的额外空间复杂度为O(n)。
C++ 代码
#include<bits/stdc++.h>
using LL = long long;
using u64 = unsigned long long;
std::mt19937_64 rng(std::chrono::steady_clock::now().time_since_epoch().count());
const int N = 300010, MOD = 998244353;
int t, n, k;
LL h[N];
int fast_power(int a, int b) {
int res = 1;
while(b) {
if(b&1) res = (LL)res*a%MOD;
a = (LL)a*a%MOD;
b >>= 1;
}
return res;
}
int inv(int x) {
return fast_power(x, MOD - 2);
}
void init() {
h[0] = 1;
for(int i = 1; i <= N - 10; i++) {
h[i] = h[i - 1]*(4LL*i%MOD - 2 + MOD)%MOD*inv(i + 1)%MOD;
}
}
int main() {
if(h[0] == 0) init();
scanf("%d", &t);
while(t--) {
scanf("%d%d", &n, &k);
std::vector<u64> d(n + 10);
for(int i = 0; i < k; i++) {
int l, r;
scanf("%d%d", &l, &r);
auto x = rng();
d[l] ^= x;
d[r + 1] ^= x;
}
for(int i = 1; i <= n; i++) {
d[i] ^= d[i - 1];
}
std::map<u64, int> cnt;
for(int i = 1; i <= n; i++) {
cnt[d[i]]++;
}
int ans = 1;
for(auto&[_, x]: cnt) {
if(x&1) {
ans = 0; // 奇数长度不可能构成合法括号串
}else {
ans = (LL)ans*h[x>>1]%MOD;
}
}
printf("%d\n", ans);
}
return 0;
}