1. 题面梗概
中译中:给定一个正则括号序列(RBS),求有多少个非空子序列,满足对其执行循环右移操作后,原序列依然是 RBS。
n≤2⋅105,∑n≤2⋅105
2. 逻辑推导
正则序列的充要条件是:任意前缀和 Pi≥0 且总和 Pn=0。
设选取的子序列下标为 {i1,i2,…,ik},循环右移后,新字符 Sij′=Si(j−2modk)+1。
对于任意前缀位置 x,设 m 是满足 im≤x 的最大索引。移位前后,前缀和 Px 的改变量 ΔPx 为:
ΔPx=j=1∑m(Sij′−Sij)
代入移位关系 Si1′=Sik,Si2′=Si1,… 继续化简:
ΔPx=(Sik−Si1)+(Si1−Si2)+⋯+(Sim−1−Sim)=Sik−Sim
由此可见,前缀和的改变量仅取决于 子序列最后一位 与 当前前缀内最后一位 的差值。
2.1 分类讨论
根据 Sik 与 Sim 的取值( ( 为 +1,) 为 −1),ΔPx 有四种可能:
-
前后均为 (:1−1=0无影响。
-
前后均为 ):−1−(−1)=0,无影响。
-
最后位是 (,替换了前缀内的 )。1−(−1)=2。前缀和增加,安全。
-
最后位是 ),替换了前缀内的 (:−1−1=−2。前缀和减少,危险。
结论:若子序列以 ) 结尾,那么对于子序列中任何处于位置 im 的 (,在区间 [im,im+1−1] 及其后续受影响区间内,必须满足原前缀和 Px≥2,以应对 −2。
2.2 统计
为了统计合法子序列,我们根据子序列结尾字符进行分类讨论。
以 ( 结尾
由于 Sik=1,ΔPx=1−Sim 永远 ≥0,这类子序列必然合法。
对于位置 i,若 Si=(,以它结尾的所有子序列(共 2i−1 个)均合法。
以 ) 结尾
设 dp[i] 为以位置 i 结尾 的 结尾为 ) 的合法子序列数。
dp[i]=1+Sj=)∑dp[j]+Sj=(,safe(j,i)∑dp[j](j<i)
其中 safe(j,i) 的条件是:在区间 [j,i−1] 之间,前缀和均 ≥2。
3. 代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| #include <bits/stdc++.h> using namespace std; using ll = long long;
const int maxn = 3e5 + 5; const int mod = 998244353;
ll dp[maxn]; ll sum[maxn], diff[maxn]; ll p2[maxn]; int nxt[maxn]; string s; int n;
void init( ) { p2[0] = 1; for( int i = 1; i < maxn; ++ i ) p2[i] = ( p2[i - 1] * 2 ) % mod; }
void solve( ) { cin >> n >> s; s = ' ' + s;
for( int i = 0; i <= n + 2; ++ i ) dp[i] = diff[i] = 0;
sum[0] = 0; for( int i = 1; i <= n; ++ i ) { int cur = ( s[i] == '(' ? 1 : -1 ); sum[i] = sum[i - 1] + cur; }
nxt[n + 1] = n + 1; for( int i = n; i >= 1; -- i ) { if( sum[i] <= 1 ) nxt[i] = i; else nxt[i] = nxt[i + 1]; }
ll ans = 0; ll suml = 0, sumr = 0;
for( int i = 1; i <= n; ++ i ) { suml = ( suml - diff[i] + mod ) % mod; dp[i] = ( 1 + suml + sumr ) % mod;
if( s[i] == '(' ) { ans = ( ans + p2[i - 1] ) % mod; suml = ( suml + dp[i] ) % mod; diff[nxt[i] + 1] = ( diff[nxt[i] + 1] + dp[i] ) % mod; } else { ans = ( ans + dp[i] ) % mod; sumr = ( sumr + dp[i] ) % mod; } }
cout << ans << '\n'; }
int main( ) { ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); init( ); int _t = 1; cin >> _t; while( _t -- ) solve( ); return 0; }
|
4. 复盘
关联笔记:线性DP | 降维技巧