Solution -「校內互測」大括號樹
\(\mathcal{Description}\)
OurTeam & OurOJ.
給定一棵 \(n\) 個頂點的樹,每個頂點標有字元 (
或 )
。將從 \(u\) 到 \(v\) 的簡單有向路徑上的字串成括號序列,記其正則匹配的子串個數為 \(\operatorname{ans}(u,v)\)。求:
\[\sum_{u=1}^n\sum_{v=1}^n\operatorname{ans}(u,v)\bmod998244353 \]
\(n\le2\times10^5\)。
\(\mathcal{Solution}\)
可以先回憶一下括號樹嗷。
來看看鏈怎麼做 owo,現有結點按 \(1\sim n\)
\[f(i)=f(\operatorname{match}(i)-1)+\operatorname{match}(i) \]
即,先保證最短的以 \(i\) 結尾的正則,起點就可以在前面任選了。而事實上,終點也能任選,那麼答案為:
\[\sum_{i=1}^nf(i)(n-i+1) \]
需要正反分別做一次嗷。
那麼,搬到樹上,一個正則會貢獻多少次呢?如圖(混V的請告訴我背景是誰吖~):
不難發現,\((u,v)\)(或 \((v,u)\))若正則匹配,則它對答案的貢獻為 \(siz_u\times siz_v\)。
好啦,開始 \(\text{DSU on Tree}\) 吧!
注意到我們只關心一些子樹大小的資訊,所以這樣設計狀態:
- \(f(u,i)\) 表示 \(u\) 子樹內某一點 \(v\) 到 \(u\),構成的串有 \(i\) 個
(
失配,且所有)
被匹配的 \(siz_v\) 之和。 - \(g(u,i)\) 表示 \(u\) 到其子樹內某一點 \(v\)
)
失配,且所有(
被匹配的 \(siz_v\) 之和。
好奇怪的定義 qwq,該怎樣理解呢?
考慮一條 \(v-u-w\) 的有向樹鏈,其中 \(u\) 是 \(v\) 與 \(w\) 的 \(\text{LCA}\)。若 \(v-u\) 長成 (...((...(
,\(u-w\) 長成 ...)...)...))
,其中 ...
是已匹配的括號。可見 \(v-u-w\) 是正則匹配的,而這正對應了我們的狀態 \(f(u,4)\) 和 \(g(u,4)\)!
接著考慮輕重兒子資訊對答案的貢獻,如圖:
\(\text{DFS}\) 輕兒子的時候,用線段樹動態維護字首的 )
,字尾的 (
是否出現失配的情況,若一個點加入後不存在失配,則用 DP 資訊更新答案。合併資訊時類似,但加入最後一個點 \(u\) 時:
-
\(s_u=\texttt{'('}\),\(f(u,i+1)=f(v,i)\),\(g(u,i-1)=f(v,i)\)。
-
\(s_u=\texttt{')'}\),\(f(u,i-1)=f(v,i)\),\(g(u,i+1)=f(v,i)\)。
這……總不可能 \(\mathcal O(siz)\) 地遍歷第二維吧 qwq。事實上,發現這只是一個單純的陣列位移,初始時開兩倍陣列,用一個指標指向陣列實際的 \(0\) 號為即可 \(\mathcal O(1)\) 實現了。
以上兩幅配圖來自 Lucky_Glass 的題解。
\(\mathcal{Code}\)
#include <cstdio>
const int MAXN = 2e5, MOD = 998244353;
int n, ecnt, head[MAXN + 5], siz[MAXN + 5], son[MAXN + 5];
int ans, aryf[MAXN * 2 + 5], aryg[MAXN * 2 + 5], *f, *g;
char s[MAXN + 5];
inline int add ( int a, const int b ) { return ( a += b ) < MOD ? a : a - MOD; }
inline int sub ( int a, const int b ) { return ( a -= b ) < 0 ? a + MOD : a; }
inline int mul ( long long a, const int b ) { return ( a *= b ) < MOD ? a : a % MOD; }
inline int rint () {
int x = 0; char s = getchar ();
for ( ; s < '0' || '9' < s; s = getchar () );
for ( ; '0' <= s && s <= '9'; s = getchar () ) x = x * 10 + ( s ^ '0' );
return x;
}
struct Edge { int to, nxt; } graph[MAXN + 5];
inline void link ( const int s, const int t ) {
graph[++ ecnt] = { t, head[s] };
head[s] = ecnt;
}
struct SegmentTree {
int mn[MAXN * 2 + 5], tag[MAXN * 2 + 5];
inline int id ( const int l, const int r ) { return ( l + r ) | ( l != r ); }
inline void pushad ( const int l, const int r, const int v ) {
int rt = id ( l, r );
mn[rt] += v, tag[rt] += v;
}
inline void pushdn ( const int l, const int r ) {
int rt = id ( l, r ), mid = l + r >> 1;
if ( ! tag[rt] ) return ;
pushad ( l, mid, tag[rt] ), pushad ( mid + 1, r, tag[rt] );
tag[rt] = 0;
}
inline void pushup ( const int l, const int r ) {
int rt = id ( l, r ), mid = l + r >> 1, lc = id ( l, mid ), rc = id ( mid + 1, r );
mn[rt] = mn[lc] < mn[rc] ? mn[lc] : mn[rc];
}
inline void update ( const int l, const int r, const int ul, const int ur, const int v ) {
if ( ul <= l && r <= ur ) return pushad ( l, r, v );
int mid = l + r >> 1; pushdn ( l, r );
if ( ul <= mid ) update ( l, mid, ul, ur, v );
if ( mid < ur ) update ( mid + 1, r, ul, ur, v );
pushup ( l, r );
}
inline bool check () { return mn[id ( 1, n )] >= 0; }
} preT, sufT; // ((... and ...)), preT->g, sufT->f.
inline void init ( const int u ) {
siz[u] = 1;
for ( int i = head[u], v; i; i = graph[i].nxt ) {
init ( v = graph[i].to ), siz[u] += siz[v];
if ( siz[son[u]] < siz[v] ) son[u] = v;
}
}
inline void update ( const int u, const int dep, const int k ) {
preT.update ( 1, n, 1, dep, s[u] == '(' ? k : -k );
sufT.update ( 1, n, 1, dep, s[u] == ')' ? k : -k );
}
inline void calc ( const int u, int cnt, const int dep ) {
cnt += s[u] == ')' ? 1 : -1, update ( u, dep, 1 );
if ( sufT.check () ) ans = add ( ans, mul ( siz[u], f[cnt] ) );
if ( preT.check () ) ans = add ( ans, mul ( siz[u], g[-cnt] ) );
for ( int i = head[u]; i; i = graph[i].nxt ) calc ( graph[i].to, cnt, dep + 1 );
update ( u, dep, -1 );
}
inline void coll ( const int u, int cnt, const int dep ) {
cnt += s[u] == '(' ? 1 : -1, update ( u, dep, -1 );
if ( sufT.check () ) f[cnt] = add ( f[cnt], siz[u] );
if ( preT.check () ) g[-cnt] = add ( g[-cnt], siz[u] );
for ( int i = head[u]; i; i = graph[i].nxt ) coll ( graph[i].to, cnt, dep + 1 );
update ( u, dep, 1 );
}
inline void solve ( const int u, const bool keep ) {
for ( int i = head[u], v; i; i = graph[i].nxt ) {
if ( ( v = graph[i].to ) ^ son[u] ) {
solve ( v, false );
}
}
if ( son[u] ) solve ( son[u], true );
if ( s[u] == '(' ) ans = add ( ans, mul ( g[1], n - siz[son[u]] ) );
if ( s[u] == ')' ) ans = add ( ans, mul ( f[1], n - siz[son[u]] ) );
for ( int i = head[u], v; i; i = graph[i].nxt ) {
if ( ( v = graph[i].to ) ^ son[u] ) {
*f = add ( *f, n - siz[v] ), g[0] = add ( *g, n - siz[v] );
update ( u, 1, 1 );
calc ( v, s[u] == ')' ? 1 : -1, 2 );
*f = sub ( *f, n - siz[v] ), g[0] = sub ( *g, n - siz[v] );
update ( u, 1, -1 );
coll ( v, 0, 1 );
}
}
if ( s[u] == '(' ) *f = add ( *f, siz[u] ), -- f, *g ++ = 0;
if ( s[u] == ')' ) *g = add ( *g, siz[u] ), -- g, *f ++ = 0;
if ( ! keep ) {
for ( int i = 0; i <= siz[u]; ++ i ) f[i] = g[i] = 0;
f = aryf + n, g = aryg + n;
}
}
int main () {
scanf ( "%d %s", &n, s + 1 );
for ( int i = 2; i <= n; ++ i ) link ( rint (), i );
init ( 1 );
f = aryf + n, g = aryg + n;
solve ( 1, true );
printf ( "%d\n", ans );
return 0;
}