即将退役选手最后的挣扎

动态dp

原理

对于树上一类支持动态修改,动态查询dp结果的问题,可以用动态dp解决。这种dp状态不能太多,不然复杂度会有问题。

其核心思想就是,我们把转移看做一个线性变化,对于重链维护线段树保存矩阵乘的答案,然后对于每个点的轻儿子的贡献当做一个常数矩阵,我们要查某个点的信息就可以在当前这个点向下延伸到底的那条重链上询问。

然后考虑修改的时候,跳到 top 然后改父亲的轻儿子的常数矩阵,大部分题都可以直接进行加减,但是对于一些特殊问题(比如切树游戏),不支持逆操作(减法或者除法)你需要多开一棵线段树维护对于每个点维护所有轻儿子的矩阵积。

然后复杂度虽然是 \(\mathcal O(L^3 n \log^2 n)\) (其中 \(L\) 为矩阵长度),但在不是特殊构造的图下效率还行。

一些小技巧

对于一些把某些状态给禁止掉的 \(dp\) ,我们可以这么做:

  • 对于计数类 \(dp\) 考虑把所有能转移上来的位置设成 \(0\)
  • 对于最优化 \(dp\) 考虑把所有能转移上来的位置设成 \(\pm \infty\)

然后需要撤回的话,考虑记一下原来的矩阵,然后按顺序改回去就行了。

一些注意事项

  • 一开始预处理静态 \(dp\) 那里,对于轻儿子的转移一定不要写错!
  • 禁止状态的时候一定不要漏或者多转移。
  • 对于叶子常常需要特殊考虑矩阵。(因为没有轻儿子)

例题

NOIP2018 保卫王国

题意

一颗 \(n\) 个点的树,每次强制两个点选还是不选,求一个最小权覆盖集。

题解

\(f_{i, 0 / 1}\)\(i\) 选还是不选的最少代价,转移显然。

记一条重链上相邻两个点为 \(j, i\)\(j\)\(i\) 的重儿子),记 \(g_{i, 0}\)\(i\) 所有轻儿子的 \(\min(f_{v, 0}, f_{v, 1})\) 的和,\(g_{i, 1}\) 为他们的 \(f_{v, 1}\) 的和,那么有:

\[\begin{bmatrix} f_{j, 0} & f_{j, 1} \end{bmatrix} \times \begin{bmatrix} \infty & g_{i, 0}\\ g_{i, 1} & g_{i, 0}\\ \end{bmatrix} = \begin{bmatrix} f_{i, 0} & f_{i, 1} \end{bmatrix} \]

然后直接维护即可。\(\mathcal O(8n \log^2 n)\)

代码

Submission #389704

「SDOI2017」切树游戏

动态维护集合幂级数 \(dp\)

戳我之前写的博客就好啦~

点分树

原理

可以把点分树看做一个比较强大的数据结构使用,通常是为了动态处理有关距离或者点对贡献的问题。

其主要思想就是维护点分治的结构,也就是对于点分树两个父子节点就是,静态点分治时候相邻的两个分治重心。

我们解决一些问题,可以先考虑静态点分治的时候如何解决,就可以考虑在点分树上动态维护这个过程了。

大概就是修改一个节点的时候,我们考虑它在点分树上到根的那条长为 \(\mathcal O(\log n)\) 的链,维护一下它对于这个分治重心的贡献;询问的时候,由于分治子树重重包含,大部分时候用容斥去掉重复贡献就行了。

一些小技巧

  • 大部分信息(例如到分治重心的距离)可以直接在静态点分治的时候预处理掉。
  • 通常为了容斥,我们要维护这个节点在点分树上到祖父的贡献。

一些注意事项

  • 如果找重心找错,会直接层数爆炸,然后获得 \(\text{MLE/RE/TLE}\)
  • 如果写法有问题的话,那个 \(sz[u]\) 不是真正的大小,我们要 \(\text{dfs}\) 一遍得到正确的大小,虽然对于找重心来说复杂度没问题,但有时候用了这个变量就会出问题。

例题

BZOJ 3730: 震波

题意

应该是较为经典的一道题了。

一棵 \(n\) 个点的树,需要支持查询到一个点 \(x\) 距离 \(\le k\) 的点权之和,以及修改一个点的权值,强制在线。

题解

询问的时候考虑在点分树上跳从 \(v\) 跳到父亲 \(u\) ,然后用 \(k\)\(dist(u, x)\) 算出对于 \(u\) 分治树内距离 \(u\) 多远的点合法。

然后直接加会有子树算多次,我们考虑每次把 \(v\) 分治树内的贡献减掉就行了。

这个直接用两个树状数组(一开始 resize )一个维护本来的贡献,一个维护对于祖父的贡献即可。

\(\mathcal O(n \log^2 n)\)

代码

戳这里,还挺好写的qwq

「ZJOI2015」幻想乡战略游戏

动态维护带权重心,以及计算带权距离。

戳我之前写的博客就好啦~

边分治

原理

顾名思义,就是对于边进行分治。这样有什么好处呢?每次我们只需要合并两边的信息,这样很多时候问题就可以大大简化。

但是直接分治找一条边使得较大子树尽量小的话,复杂度会在菊花图时退化成 \(\mathcal O(n^2)\) 的。

考虑优化,我们显然只需要保证度数,那么复杂度就是正确的了,这个只需要二叉化。给一种比较方便的实现:

void Build(int u, int fa = 0) {
	int lst = u;
	Travel(i, u, T) if (v != fa)
		Build(v, u), DT.Add(lst, node, 0), DT.Add(node, v, w), lst = node;
}

什么意思呢?就是每次在上一次后面接一个新点就行了,好像点数还比较优秀。

然后这样处理合并问题就十分的方便。

但是二叉化后有一点不好,就是不能保证原图上的两个点之间的路径一定会在新图中出现,似乎有两种解决方案?

  • 我们发现只会有 \(lca\) 不在这条路径上,特判掉。
  • 把点的信息拆到新建的虚点上,但是好像只能把 \(\min, \max\) 拆上来,对于求和的似乎不好拆?

如果有大佬会比较通用的解决方法,欢迎 \(D\) 我。

一些小技巧

  • 由于边分的那条边不太好处理,我们可以一边给 \(w\) 的权值,另外一边为 \(0\)
  • 然后我们可以把两边定向 \(0/1\) ,可以唯一确定一个分治块所在的位置,也就是边分树的结构(边分树是一个深度为 \(\mathcal O(\log n)\) 的二叉树)。

一些注意事项

  • 注意传下去的 \(sz\) 这时候传错的话复杂度就真错了,一边是 \(sz_x\) 另外一遍是 \(tot - sz_x\)
  • 二叉化后的点数会乘个常数,不要少开空间。

例题

BZOJ2870 最长道路

题意

一棵 \(n\) 个点的树,求一条路径使得路径上点权最小值 \(\times\) 路径长度最大。

题解

有很多高妙的做法,为了练板子写个边分治啦。。。

考虑二叉化的时候新点的权值,其实就是 \(val_u\)

然后边分治后把两边的边分别按路径上点权最小值从大到小排序,用 \(\text{two-pointers}\) 扫一下算算答案就行了。

复杂度是 \(\mathcal O(n \log^2 n)\) 的。

「CTSC2018」暴力写挂

边分治合并好题。

戳我之前写的博客就好啦~

长链剖分

原理

类似于重链剖分,长链剖分是把向下延伸最长的那个儿子当做重儿子。

有什么好处呢?它在合并与深度有关的信息可以做到 \(\mathcal O(n)\) 啦。

每次就暴力把轻儿子的信息暴力合并到重儿子上,这样是 \(\sum\) 重链长的。

一些小技巧

  • 虽然是 \(\mathcal O(n)\) 的,但开空间是一个比较重要的问题,怎么做呢。我们同样可以类似于重链剖分那样按 \(dfs\) 序给每个点标号即可,或者开个 deque 也行。
  • 如果开 deque 记得及时 clear 。。deque 维护空间,常数不小。。

BZOJ 3653: 谈笑风生

模板题,统计子树内距 \(u\) 距离不超过 \(k\) 的权值和。

戳我之前写的博客就好啦~

UOJ#284. 快乐游戏鸡

题意

一棵 \(n\) 个点的有根树,带点权 \(w_i\)

\(s\) 出发,希望达到 \(t\) ,每秒可以从当前点移动到某一个儿子。

有一个死亡次数,初始为 \(0\) 。若在某个点 \(i(i \not = s, t)\) 时,死亡次数 \(\le w_i\) ,那么死亡次数自增 ,\(1\)并且立刻跳回到 \(s\)

给出 \(q\)\(s, t\) ,求最短时间。

\(n, q \le 3 \times 10^5\)

题解

显然每次死亡都相互独立。

\(f_{i,j}\) 为当前在 \(i\) ,已死亡 \(j − 1\) 次,再死一次所需时间。若 \(s, t\) 路径上最大点权为 \(W\) ,那么答案为 \(\sum_{i=1}^W f_{s,i} + dis(s, t)\)

这样设计状态的复杂度太大,发现 \(f\) 单调并且有大量状态相同。

优化:\(g_{i,j}\) ,从 \(i\) 出发,前多少次死亡的消耗时间不超过 \(j\) ( \(i\) 子树内与其距离不超过 \(j\) 的点中,最大权值)。

然后我们想一下这个如何维护,可以优先考虑序列上如何解决,我们从后往前维护对于 \(w_i\) 的一个单调递增的单调栈,然后每次询问的时候在栈上二分第一个 \(i\) 满足 \(w_i \ge W\) ,动态维护一下后缀和算一下贡献即可。

放到树上,我们可以考虑把这个单调栈启发式合并即可。具体来说就是把大的前小的 \(size\) 个全部拿出来,然后这些按顺序逐个插到单调栈前面即可。

复杂度是 \(\mathcal O((n + q) \log n)\) 的,跑的还挺快qwq

代码

UOJ Submission #337191

其他的一些树上数据结构以及算法

不想重新写了。。看我 之前的总结 算了。。。

各种套路以及解法写的还挺详细的。。

决策单调性优化dp

原理

我们可以跑一个暴力 \(dp\) 观察,或者 感性理解 证明一波决策单调性。

我们通常有有两种解决办法,各有利弊。

  • 维护决策时间单调的一个单调栈,二分找出后者比前者优的时刻;
  • 分治区间中点决策最优点,递归左右处理。

第一种在处理 \(f_i = \min_{j < i} (f_j + s(j, i))\) 类似的问题时候是必要的。

对于第二种如果先递归左区间,那么复杂度是错误的,但是有两个好处:好写;以及处理 \(s(j, i)\) 不是那么好算的函数时,可以类似与莫队一样计算。(例如区间相同数字对数)

一些小技巧

  • 分层 \(dp\) or 求一个最优区间,通常使用第二种做法。
  • 二分找时刻,相当于二分对于这两个函数相交点。

一些注意事项

  • 对于决策单调性有两种表述形式:

    • \(j < k\) 在某个时刻 \(j\)\(k\) 优,那么 \(j\) 永远比 \(k\) 优;
    • \(j < k\) 在某个时刻 \(k\)\(j\) 优,那么 \(k\) 永远比 \(j\) 优。

    这两种是需要不同的做法!!后面会有两道例题介绍为什么是不同的。

例题

[NOI2009]诗人小G

题意

把整个序列划分成很多段,每段的权值是 \((S_i - S_j - L)^P\) 最小化总权值。

题解

这是介绍的最为经典与常见的前面说的第二种情况,我们依次考虑加入,由于每次决策区间要优的话肯定是插在后面,所以直接和最后一个比一下就行了。

挂个之前写的链接 qwq

[JSOI2011] 柠檬

题意

把整个序列划分成很多段,每段选择其中一个元素 \(a\) 设其出现次数为 \(t\) ,它的权值是 \(at^2\) ,最大化总权值。

题解

我们发现这个有些不同的地方,也就是求最大化权值,显然这是对应的第一种情况,做法是显然不同的。

每次选择的区间两个端点的元素是一样的时候不劣。然后可以我们可以对于每个元素维护一个单调栈维护决策点。

但是我们需要维护单调栈中的元素满足,每一个元素超过上一个元素的时间也是单调的。

这个维护没有那么直观,我们需要一些小东西。

对于任意的 \(j1<j2<i1<i2\) ,可以发现如果 \(j1\) 超过 \(i1\) 的时间小于 \(j2\) 超过 \(i1\) 的时间,那么 \(j1\) 超过 \(i2\) 的时间也一定比 \(j2\) 超过 \(i2\) 的时间早。

\(beyond(x, y)\)\(x\) 超过 \(y\) 的时间,这个可以二分零点求。

那么我们要保证栈中元素的时间单调,考虑栈顶的两个元素,如果 \(beyond(stk[top - 1], stk[top]) \le beyond(stk[top], i)\) 那么意味着 \(stk[top]\) 在这之后都不如别的优秀。

然后如果 \(beyond(stk[top - 1], stk[top]) \le i\) 那么以后 \(stk[top]\) 都不如 \(stk[top - 1]\) 优,都弹掉就行了。

其实可以把之前那个单调栈倒着做,因为每次更新的区间都在最前面了,似乎是差不多的。

代码

挂这啦~

JOISC Day4T1 cake3

题意

\(n\) 块蛋糕,每块蛋糕有两个权值 \(V_i\)\(C_i\) 。你需要选出其中的 \(m\) 个排成一个圆排列,设排列\(p_1,p_2...p_m\)(定义 \(p_{m+1}=p1\) ),则收益为 \(\sum_{i=1}^m V_{p_i} −\sum_{i=1}^m|C_{p_i}−C_{p_i} + 1|\) 。求最大收益。

\(n,m \le 2 \times 10^5\)

题解

显然对于选出的 \(m\) 块蛋糕肯定从小到大排序,每次选择一段连续的区间。

对于每个区间(区间长度 \(\ge m\) )的权值其实就是从中选出前 \(m\) 大的数然后减去 \(2(C_r - C_l)\)

考虑 \(l\) 从小到大,每次选择右端点是单调不降的,用决策单调性就好啦。

似乎这个只能主席树做到 \(\mathcal O(n \log^2 n)\)

注意一些坑点:答案可能为负数,主席树到最下面节点的时候可能不选完。

CodeForces 868F Yet Another Minimization Problem

这个就是那个权值不能快速算的题啦,戳 这里 qwq

群论计数

原理

求解在置换下本质不同的方案数的时候,我们通常使用 \(\text{Burnside}\) 引理/ \(\text{Polya}\) 定理辅助计数。

\(\mathrm{Burnside}\) 引理:

\[L = \frac{1}{|G|} \sum_{i = 1}^{|G|} c_1(g_i) \]

其中 \(G=\{g_1, ..., g_s\}\) ,其中 \(c_1(g_i)\) 为所有染色方案在 \(g_i\) 置换下不发生改变的数量(也就是不动点个数),\(L\) 为本质不同的方案数。

这个在特殊情况下才要用,更多时候它的过于繁琐,我们有更方便的 \(\text{Polya}\) 定理。

\(\mathrm{Polya}\) 定理:

\[L=\frac{1}{|G|}\sum \limits _{i=1} ^{|G|} m^{c(g_i)} \]

其中 \(G=\{g_1, ..., g_s\}\) ,其中 \(c(g_i)\) 为置换 \(g_i\) 的循环节个数,\(m\) 是可选择的颜色种数,\(L\) 为本质不同的方案数。

就是上面这两个定理啦qwq

一些小技巧

  • 如果我们用 \(\text{Burnside}\) 引理,可以类似于 \(\text{Polya}\) 定理来进行计数,也就是对于每个置换,我们需要知道它的每个循环的长度,显然每个循环内只能放一样的元素。就可以跑 \(dp\) 或者各种奇怪计数了。
  • 如果我们用 \(\text{Polya}\) 定理,那么我们只需要知道循环节的个数。对于特殊类型的置换,每个元素所在循环节的长度是一样的,我们只需要求出循环节的长度,就可以知道循环节的个数啦。

一些注意事项

  • 用这些定理的时候,首先是要满足群的封闭性,也就是对于任意两个群上的置换结合后的结果,仍然是群上的一个置换。

例题

[HNOI2008]Cards

题意

\(Sr\) 张红牌,\(Sb\) 张蓝色,\(Sg\) 张绿色,给 \(m\) 个置换构成的群(保证封闭性),求洗牌的方式。

\(\max\{Sr, Sb, Sg\} \le 20, m \le 60\)

题解

套用 \(\text{Burnside}\) 引理,我们就是要求每个置换的不动点个数,我们找出每个循环节的长度,直接用个背包计数即可。

注意,不要漏了还有 不动 这个置换。

\(\mathcal O(mS^3)\)

UVA10294 Arif in Dhaka

经典的求翻转和旋转的染色方案,戳俺的题解qwq

[Shoi2006]color

经典的求 \(n\) 点完全图 \(m\) 染色非同构数问题,戳这里QAQ

[HNOI2009]图的同构记数

题意

求有多少个本质不同的 \(n\) 个点的简单图。\(n \le 60\)

题解

显然把每条边当做可以染黑白两种颜色,代表出现/不出现。

那么就是 \(m = 2\) 的上一题啦2333

点双联通分量(圆方树)

原理

对于一些任意图(或仙人掌)问题,我们通常可以考虑它的圆方树。

简单来说就是给每一个点双新建一个方点,然后连向这个点双中的每一个圆点(原图上的点)。

点双咋求? \(Tarjan\) 记录一下 \(lowlink\) 就好啦。

特别的,把两个点互相连通的也视作一个点双。

这有什么好处呢?我们可以把原图上两个点的路径视作圆方树上的路径,方点的时候特殊处理一下,圆点直接走,并且路径上的所有圆点必然是要经过的。

这样对于大部分的题目,我们可以方点特殊处理,圆点直接当做树上问题解决了。

一些小技巧

  • 新建的方点编号从 \(n + 1\) 开始就很好区分圆点和方点了。

一些注意事项

  • 对于路径 \(u, v\)\(lca\) 是方点的时候需要特殊考虑。
  • 圆方树点数可能是 \(2\) 倍,数组记得要开大,并且后面预处理树上信息的时候,点数不要写成 \(n\)
  • 图可能不联通,记得遍历每个联通分量处理。

例题

[APIO2018] Duathlon 铁人两项

题意

一个 \(n\)\(m\) 条边的无向图,求有多少个三元组 \((s, c, f)\) 满足从 \(s \to f\) 的简单路径经过了 \(c\)

题解

对于一对点 \((s, f)\) 的贡献就是路径可以经过上的节点个数。

将方点的权值标为点双大小,圆点的权值标为 \(-1\) ,则某条路径上可能经过的点数就是圆方树上路径点权和。

然后只需要统计每个点被多少条路径经过,也就是 \(sz(tot - sz)\)

\(\mathcal O(n)\)

「SDOI2018」战略游戏

题意

一个 \(n\)\(m\) 条边的无向图,\(q\) 次给一个点集 \(S\) ,求有多少个点删掉后(不能为 \(S\) 集合里的点),可以使得 \(S\) 不联通。

题解

考虑圆方树上哪些点可以当做点集的割点。

显然不能是方点,一定是圆点,然后它子树内外都至少有一个点集中的点即可。

那么每次虚树算一下答案即可。

\(\mathcal O(n \log n)\)

代码

Submission #392844

【CF Round #278】Tourists

题意

一个 \(n\)\(m\) 条边的无向联通图,每个点有一个点权,支持动态修改点权。

\(q\) 次询问,求从 \(u\)\(v\) 的所有简单路径中经过的最小点权是多少。

题解

可以建出圆方树,在每个方点上维护这个点双中的最小点权,那么每次询问就是查询一个路径最小值了。

有修改,如果直接改完圆点后改和它相邻的方点,显然复杂度有问题。可以卡成 \(\mathcal O(n^2)\)

每个方点维护的信息中不包括它的父亲圆点,这样修改圆点的时候就只需要修改它的父亲方点。查询的时候如果路径的 \(lca\) 是方点,才算上面那个父亲圆点。

我们利用 链剖线段树+可删除堆 维护一下就好了。

\(\mathcal O(n \log^2 n)\)

代码

Submission #280933

割点/桥/边双联通分量

原理

好像这些没啥用。。

割点咋求? \(Tarjan\) 的时候,判断一下子树内是否有点能伸出来即可,特判根只有一个儿子的情况。

桥咋求? \(Tarjan\) 的时候,判断一下子树内是否有边能伸出即可。

边双咋求?把桥边断了,剩下的每个联通分量就是边双了。

好像很简单的样子。。

一些小技巧

  • 求边双似乎可以先求 \(dfs\) 树,然后每次用并查集把路径上的点缩起来。

一些注意事项

  • 注意有重边的时候,求桥边需要标记边是否经过,而不是只判父亲。

例题

[HNOI2012]矿场搭建

题意

\(m\) 条边的无向图,计算至少需要设几个特殊点,满足每个点删去后后,剩下的所有点都至少能到一个特殊点。还是求方案数。

题解

\(Tarjan\) 跑出割点,然后割开后对于每个联通块单独考虑。(也就是每个点双联通分量)

如果当前没有割点,因为放一个特殊点可能被删掉,那么放两个特殊点。

如果只有一个割点,那么割点删了就怎么也出不去了,所以要设一个特殊点。

两个以上的话,删哪个都能跑到有出口的地方,就不用设置了。

\(\mathcal O(n + m)\)

POJ3177 Redundant Paths

题意

一个 \(n\)\(m\) 条边的无向联通图,问至少要加多少条边才能使得整个图边双联通。

题解

首先把边双联通分量缩一起,就成了一棵树。

对于树来说,为了使得每个叶子都在双联通分量里,那么叶子必定连边,不难发现叶子两两连边是最优的,此时答案达到了下界。

\(\mathcal O(n + m)\)

强连通分量/Two-Sat

原理

对于有向图,我们对于强连通分量特殊处理,然后利用缩点后的 \(DAG\) 跑一些 \(dp\)

然后对于一类真假判定性问题,我们如果每次都是二元关系,可以利用 \(\text{Two-Sat}\) 解决问题。

怎么做?看我之前的笔记 qwq

大概就是真假状态根据推导关系进行连边,然后跑个缩点就好啦。

一些小技巧

  • 由于 \(\text{Two-Sat}\) 我们常常要连逆否命题的边,所以可以单独写个函数来把两条边都连上。
  • 暴力建图一般边数较多,可以尝试一波很多点连到一起减少边数。
  • 有时候可能一个数可能有很多个取值,但是他们存在性如果是依赖的,也可以上 \(\text{Two-Sat}\)

一些注意事项

  • 还是和前面很多算法一样,数组不要开小。
  • 输出方案的时候,是拓扑序较大,也就是 \(sccno\) 较小的在前。

例题

[SDOI2010] 所驼门王的宝藏

题意

有一个 \(r \times c\) 的网格,网格的 \(n\) 个格子放着宝藏,每个有宝藏的格子都有一个传送门,传送门是以下三种中的一种:

  • “横天门“:由该门可以传送到同行的任一格子;
  • “纵寰门”:由该门可以传送到同列的任一格子;
  • ”自闭门“:由该门可以传送到以该门所在格子为中心周围8格
    中任一格子。

找一条路径(起点终点任意,可以经过重复的点)经过尽量多的宝藏(重复经过只算一次)。

\(n \le 10^5, r, c \le 10^6\)

题解

从传送门直接向可以传送到的有宝藏的格子连边,然后缩强连通分量,得到一个 \(DAG\)

答案就是 \(DAG\)\(size\) 之和最大的链,可以简单地拓扑 \(dp\) 得到答案。但这样边数是 \(\mathcal O(n^2)\) 的。

对于所有行和列额外建一个点,向这行/列的每一个有宝藏的格子连边,然后有传送整行/列的传送门就可以直接向新建的点连边了。

\(\mathcal O(r + c + n)\)

51nod 1318 最大公约数与最小公倍数方程组

一道看起来没那么 \(\text{Two-Sat}\)\(\text{Two-Sat}\)

原来写的题解 QAQ。。

这启示我们,对于多维最大最小值限制的题,也可以使用 \(\text{Two-Sat}\)

LOJ #6036.「雅礼集训 2017 Day4」编码

题意

给出 \(n\)\(0/1\) 串,每个 \(0/1\) 串中至多有一个 \(?\) 。问是否存在一种在 \(?\) 处填 \(0/1\) 的方案,使得 \(n\) 个串中,不存在一个串是另一个串的前缀。

\(n, \sum |S| \le 5 \times 10^5\)

题解

不难发现是一个 \(\text{Two-Sat}\) 模型,可以直接枚举每两个串判断是否冲突进行连边,但这样是平方的。

由于限制和前缀有关,可以想到用Trie来优化建边。

一种做法是,\(Trie\) 上每个节点 \(u\) 记一个辅助变量 \(f_u\) ,表示这个子树中是否有串被选择。

把串按长度从大到小加入 \(Trie\) ,并将 \(Trie\) 可持久化,连边比较简单,只需保证加入的这个串与之前的串没有冲突即可。

大概就是枚举每个 \(?\)\(0 / 1\) 取值。当到串尾的时候克隆一个新节点出来,然后之前子树内取值都不能为 \(1\) ,然后当前的状态向新节点的取值 \(1\) 的状态连边。然后每个点的状态其实就是一个前缀状态。

\(\mathcal O(n \log n + |S|)\)

代码

Submission #394323

posted @ 2019-03-29 16:34  zjp_shadow  阅读(1091)  评论(1编辑  收藏  举报