\(\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\) 从左到右编号,记 \(s(i,j)\) 表示从 \(i\) 到 \(j\) 串成的括号序列。令 \(\operatorname{match}(i)\) 为最大的 \(j<i\),满足 \(s(j,i)\) 正则匹配。定义状态 \(f(i)\) 表示 \(s(1,i)\) 中,以 \(i\) 结尾的正则子串贡献。那么:

\[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\),构成的串有 \(i\) 个 ) 失配,且所有 ( 被匹配的 \(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}\)

  1. #include <cstdio>
  2. const int MAXN = 2e5, MOD = 998244353;
  3. int n, ecnt, head[MAXN + 5], siz[MAXN + 5], son[MAXN + 5];
  4. int ans, aryf[MAXN * 2 + 5], aryg[MAXN * 2 + 5], *f, *g;
  5. char s[MAXN + 5];
  6. inline int add ( int a, const int b ) { return ( a += b ) < MOD ? a : a - MOD; }
  7. inline int sub ( int a, const int b ) { return ( a -= b ) < 0 ? a + MOD : a; }
  8. inline int mul ( long long a, const int b ) { return ( a *= b ) < MOD ? a : a % MOD; }
  9. inline int rint () {
  10. int x = 0; char s = getchar ();
  11. for ( ; s < '0' || '9' < s; s = getchar () );
  12. for ( ; '0' <= s && s <= '9'; s = getchar () ) x = x * 10 + ( s ^ '0' );
  13. return x;
  14. }
  15. struct Edge { int to, nxt; } graph[MAXN + 5];
  16. inline void link ( const int s, const int t ) {
  17. graph[++ ecnt] = { t, head[s] };
  18. head[s] = ecnt;
  19. }
  20. struct SegmentTree {
  21. int mn[MAXN * 2 + 5], tag[MAXN * 2 + 5];
  22. inline int id ( const int l, const int r ) { return ( l + r ) | ( l != r ); }
  23. inline void pushad ( const int l, const int r, const int v ) {
  24. int rt = id ( l, r );
  25. mn[rt] += v, tag[rt] += v;
  26. }
  27. inline void pushdn ( const int l, const int r ) {
  28. int rt = id ( l, r ), mid = l + r >> 1;
  29. if ( ! tag[rt] ) return ;
  30. pushad ( l, mid, tag[rt] ), pushad ( mid + 1, r, tag[rt] );
  31. tag[rt] = 0;
  32. }
  33. inline void pushup ( const int l, const int r ) {
  34. int rt = id ( l, r ), mid = l + r >> 1, lc = id ( l, mid ), rc = id ( mid + 1, r );
  35. mn[rt] = mn[lc] < mn[rc] ? mn[lc] : mn[rc];
  36. }
  37. inline void update ( const int l, const int r, const int ul, const int ur, const int v ) {
  38. if ( ul <= l && r <= ur ) return pushad ( l, r, v );
  39. int mid = l + r >> 1; pushdn ( l, r );
  40. if ( ul <= mid ) update ( l, mid, ul, ur, v );
  41. if ( mid < ur ) update ( mid + 1, r, ul, ur, v );
  42. pushup ( l, r );
  43. }
  44. inline bool check () { return mn[id ( 1, n )] >= 0; }
  45. } preT, sufT; // ((... and ...)), preT->g, sufT->f.
  46. inline void init ( const int u ) {
  47. siz[u] = 1;
  48. for ( int i = head[u], v; i; i = graph[i].nxt ) {
  49. init ( v = graph[i].to ), siz[u] += siz[v];
  50. if ( siz[son[u]] < siz[v] ) son[u] = v;
  51. }
  52. }
  53. inline void update ( const int u, const int dep, const int k ) {
  54. preT.update ( 1, n, 1, dep, s[u] == '(' ? k : -k );
  55. sufT.update ( 1, n, 1, dep, s[u] == ')' ? k : -k );
  56. }
  57. inline void calc ( const int u, int cnt, const int dep ) {
  58. cnt += s[u] == ')' ? 1 : -1, update ( u, dep, 1 );
  59. if ( sufT.check () ) ans = add ( ans, mul ( siz[u], f[cnt] ) );
  60. if ( preT.check () ) ans = add ( ans, mul ( siz[u], g[-cnt] ) );
  61. for ( int i = head[u]; i; i = graph[i].nxt ) calc ( graph[i].to, cnt, dep + 1 );
  62. update ( u, dep, -1 );
  63. }
  64. inline void coll ( const int u, int cnt, const int dep ) {
  65. cnt += s[u] == '(' ? 1 : -1, update ( u, dep, -1 );
  66. if ( sufT.check () ) f[cnt] = add ( f[cnt], siz[u] );
  67. if ( preT.check () ) g[-cnt] = add ( g[-cnt], siz[u] );
  68. for ( int i = head[u]; i; i = graph[i].nxt ) coll ( graph[i].to, cnt, dep + 1 );
  69. update ( u, dep, 1 );
  70. }
  71. inline void solve ( const int u, const bool keep ) {
  72. for ( int i = head[u], v; i; i = graph[i].nxt ) {
  73. if ( ( v = graph[i].to ) ^ son[u] ) {
  74. solve ( v, false );
  75. }
  76. }
  77. if ( son[u] ) solve ( son[u], true );
  78. if ( s[u] == '(' ) ans = add ( ans, mul ( g[1], n - siz[son[u]] ) );
  79. if ( s[u] == ')' ) ans = add ( ans, mul ( f[1], n - siz[son[u]] ) );
  80. for ( int i = head[u], v; i; i = graph[i].nxt ) {
  81. if ( ( v = graph[i].to ) ^ son[u] ) {
  82. *f = add ( *f, n - siz[v] ), g[0] = add ( *g, n - siz[v] );
  83. update ( u, 1, 1 );
  84. calc ( v, s[u] == ')' ? 1 : -1, 2 );
  85. *f = sub ( *f, n - siz[v] ), g[0] = sub ( *g, n - siz[v] );
  86. update ( u, 1, -1 );
  87. coll ( v, 0, 1 );
  88. }
  89. }
  90. if ( s[u] == '(' ) *f = add ( *f, siz[u] ), -- f, *g ++ = 0;
  91. if ( s[u] == ')' ) *g = add ( *g, siz[u] ), -- g, *f ++ = 0;
  92. if ( ! keep ) {
  93. for ( int i = 0; i <= siz[u]; ++ i ) f[i] = g[i] = 0;
  94. f = aryf + n, g = aryg + n;
  95. }
  96. }
  97. int main () {
  98. scanf ( "%d %s", &n, s + 1 );
  99. for ( int i = 2; i <= n; ++ i ) link ( rint (), i );
  100. init ( 1 );
  101. f = aryf + n, g = aryg + n;
  102. solve ( 1, true );
  103. printf ( "%d\n", ans );
  104. return 0;
  105. }

\(\mathcal{Update}\)

  然后你就发现……只需要维护前缀、后缀最大值,与当前前缀、后缀和比较就砍掉 \(\log\) 了 owo!

  1. #include <cstdio>
  2. const int MAXN = 2e5, MOD = 998244353;
  3. int n, ecnt, head[MAXN + 5], siz[MAXN + 5], son[MAXN + 5];
  4. int ans, aryf[MAXN * 2 + 5], aryg[MAXN * 2 + 5], *f, *g;
  5. char s[MAXN + 5];
  6. inline int add ( int a, const int b ) { return ( a += b ) < MOD ? a : a - MOD; }
  7. inline int sub ( int a, const int b ) { return ( a -= b ) < 0 ? a + MOD : a; }
  8. inline int mul ( long long a, const int b ) { return ( a *= b ) < MOD ? a : a % MOD; }
  9. inline void chkmax ( int& a, const int b ) { if ( a < b ) a = b; }
  10. inline int rint () {
  11. int x = 0; char s = getchar ();
  12. for ( ; s < '0' || '9' < s; s = getchar () );
  13. for ( ; '0' <= s && s <= '9'; s = getchar () ) x = x * 10 + ( s ^ '0' );
  14. return x;
  15. }
  16. struct Edge { int to, nxt; } graph[MAXN + 5];
  17. inline void link ( const int s, const int t ) {
  18. graph[++ ecnt] = { t, head[s] };
  19. head[s] = ecnt;
  20. }
  21. inline void init ( const int u ) {
  22. siz[u] = 1;
  23. for ( int i = head[u], v; i; i = graph[i].nxt ) {
  24. init ( v = graph[i].to ), siz[u] += siz[v];
  25. if ( siz[son[u]] < siz[v] ) son[u] = v;
  26. }
  27. }
  28. inline void calc ( const int u, int cnt, int pre, int suf ) {
  29. cnt += s[u] == ')' ? 1 : -1;
  30. chkmax ( pre, -cnt ), chkmax ( suf, cnt );
  31. if ( suf == cnt ) ans = add ( ans, mul ( siz[u], f[cnt] ) );
  32. if ( pre == -cnt ) ans = add ( ans, mul ( siz[u], g[-cnt] ) );
  33. for ( int i = head[u]; i; i = graph[i].nxt ) calc ( graph[i].to, cnt, pre, suf );
  34. }
  35. inline void coll ( const int u, int cnt, int pre, int suf ) {
  36. cnt += s[u] == '(' ? 1 : -1;
  37. chkmax ( pre, -cnt ), chkmax ( suf, cnt );
  38. if ( suf == cnt ) f[cnt] = add ( f[cnt], siz[u] );
  39. if ( pre == -cnt ) g[-cnt] = add ( g[-cnt], siz[u] );
  40. for ( int i = head[u]; i; i = graph[i].nxt ) coll ( graph[i].to, cnt, pre, suf );
  41. }
  42. inline void solve ( const int u, const bool keep ) {
  43. for ( int i = head[u], v; i; i = graph[i].nxt ) {
  44. if ( ( v = graph[i].to ) ^ son[u] ) {
  45. solve ( v, false );
  46. }
  47. }
  48. if ( son[u] ) solve ( son[u], true );
  49. if ( s[u] == '(' ) ans = add ( ans, mul ( g[1], n - siz[son[u]] ) );
  50. if ( s[u] == ')' ) ans = add ( ans, mul ( f[1], n - siz[son[u]] ) );
  51. int pre = s[u] == '(' ? 1 : -1, suf = s[u] == ')' ? 1 : -1;
  52. for ( int i = head[u], v; i; i = graph[i].nxt ) {
  53. if ( ( v = graph[i].to ) ^ son[u] ) {
  54. *f = add ( *f, n - siz[v] ), g[0] = add ( *g, n - siz[v] );
  55. calc ( v, s[u] == ')' ? 1 : -1, pre, suf );
  56. *f = sub ( *f, n - siz[v] ), g[0] = sub ( *g, n - siz[v] );
  57. coll ( v, 0, 0, 0 );
  58. }
  59. }
  60. if ( s[u] == '(' ) *f = add ( *f, siz[u] ), -- f, *g ++ = 0;
  61. if ( s[u] == ')' ) *g = add ( *g, siz[u] ), -- g, *f ++ = 0;
  62. if ( ! keep ) {
  63. for ( int i = 0; i <= siz[u]; ++ i ) f[i] = g[i] = 0;
  64. f = aryf + n, g = aryg + n;
  65. }
  66. }
  67. int main () {
  68. scanf ( "%d %s", &n, s + 1 );
  69. for ( int i = 2; i <= n; ++ i ) link ( rint (), i );
  70. init ( 1 );
  71. f = aryf + n, g = aryg + n;
  72. solve ( 1, true );
  73. printf ( "%d\n", ans );
  74. return 0;
  75. }

Solution -「LOCAL」大括号树的更多相关文章

  1. Solution -「LOCAL」Drainage System

    \(\mathcal{Description}\)   合并果子,初始果子的权值在 \(1\sim n\) 之间,权值为 \(i\) 的有 \(a_i\) 个.每次可以挑 \(x\in[L,R]\) ...

  2. Solution -「LOCAL」Burning Flowers

      灼之花好评,条条生日快乐(假装现在 8.15)! \(\mathcal{Description}\)   给定一棵以 \(1\) 为根的树,第 \(i\) 个结点有颜色 \(c_i\) 和光亮值 ...

  3. Solution -「LOCAL」画画图

    \(\mathcal{Description}\)   OurTeam.   给定一棵 \(n\) 个点的树形随机的带边权树,求所有含奇数条边的路径中位数之和.树形生成方式为随机取不连通两点连边直到全 ...

  4. Solution -「LOCAL」ZB 平衡树

    \(\mathcal{Description}\)   OurOJ.   维护一列二元组 \((a,b)\),给定初始 \(n\) 个元素,接下来 \(m\) 次操作: 在某个位置插入一个二元组: 翻 ...

  5. Solution -「LOCAL」人口迁徙

    \(\mathcal{Description}\)   \(n\) 个点,第 \(i\) 个点能走向第 \(d_i\) 个点,但从一个点出发至多走 \(k\) 步.对于每个点,求有多少点能够走到它. ...

  6. Solution -「LOCAL」「cov. HDU 6864」找朋友

    \(\mathcal{Description}\)   Link.(几乎一致)   给定 \(n\) 个点 \(m\) 条边的仙人掌和起点 \(s\),边长度均为 \(1\).令 \(d(u)\) 表 ...

  7. Solution -「LOCAL」模板

    \(\mathcal{Description}\)   OurOJ.   给定一棵 \(n\) 个结点树,\(1\) 为根,每个 \(u\) 结点有容量 \(k_u\).\(m\) 次操作,每次操作 ...

  8. Solution -「LOCAL」割海成路之日

    \(\mathcal{Description}\)   OurOJ.   给定 \(n\) 个点的一棵树,有 \(1,2,3\) 三种边权.一条简单有向路径 \((s,t)\) 合法,当且仅当走过一条 ...

  9. Solution -「LOCAL」二进制的世界

    \(\mathcal{Description}\)   OurOJ.   给定序列 \(\{a_n\}\) 和一个二元运算 \(\operatorname{op}\in\{\operatorname{ ...

随机推荐

  1. Centos7 文件修改详情

    Centos常规修改信息 记录文件在系统中的意义 /etc/locale.conf ---修改字符集文件 /etc/profile ---修改环境变量

  2. nuxt 项目安装及环境配置

    babel篇 在package.json中添加--exec babel-node 如果需要编译es6,我们需要设置presets包含es2015,也就是预先加载es6编译的模块. 如果需要编译es7, ...

  3. less 循环模拟sass的for循环效果

    // 输入框部分宽度 从10px到600px 相隔10像素 .generate-widths(600); .generate-widths(@n, @i: 10) when (@i =< @n) ...

  4. 在实验中观察指针——C++ 函数参数的压栈顺序

    前言 好久没写东西了,突发奇想,写写函数参数的压栈顺序 先看看这个问题 https://q.cnblogs.com/q/137133/ 然后看我简化的代码,猜输出结果是多少? #include< ...

  5. 07.python函数作用域global、nonlocal、LEGB

    函数作用域 作用域 一个标识符的课件范围,这就是标识符的作用域,一般常说的是变量的作用域 def foo():    x = 100 print(x) # 可以访问到吗 上例中x不可以访问到,会抛出异 ...

  6. 函数实现将 DataFrame 数据直接划分为测试集训练集

     虽然 Scikit-Learn 有可以划分数据集的函数 train_test_split ,但在有些特殊情况我们只希望它将 DataFrame 数据直接划分为 train, test 而不是像 tr ...

  7. Python函数与lambda 表达式(匿名函数)

    Python函数 一.函数的作用 函数是组织好的,可重复使用的,用来实现单一或相关联功能的代码段 函数能提高应用的模块性和代码的重复利用率 python 内置函数:https://docs.pytho ...

  8. 通过HTML+CSS+Javascript实现向下滚动滚动条出现导航栏并出现回到顶部按钮点击按钮回到顶部(一)

    回到顶部实例一 效果:默认隐藏导航栏,当滚动条滚到超过300px后导航栏和按钮出现,点击回到顶部按钮回到顶部,并隐藏导航栏和按钮(导航栏和按钮都是固定定位) <!doctype html> ...

  9. 【算法】nSum问题

    LeetCode中出现了2sum, 3sum, 4sum的问题,文章给出了一种通用的解法,想法是将n_sum问题转换为(n-1)_sum问题,具体步骤如下: 定义函数sum(n, target),表示 ...

  10. Anaconda 创建 32位python虚拟环境

    Anaconda 创建 32位python虚拟环境 ​ 最近实习在做一个接口自动化数据上传的功能,因为数据是更新的,需要每次上传都查询数据库调用匹配,就不得不面对 python 连接 oracle . ...