好题题集3
Podatki drogowe
给定 \(n\) 个点的树,第 \(i\) 条无向边的边权为 \(n^{p_i}\)。
求第 \(k\) 短的路径长度 \(\mod 10^9 + 7\)。
\(2 \le n \le 25000, 1 \le \frac{n(n-1)}{2}, 1 \le p_i \le n,tl = 7s, ml = 1024MB\)
可以做随机化二分的板子题了(虽然挂了边分治,主席树之类的东西),也可以做权为幂的相关运算的板子题了。
其实 小芈 也可以做板子题了,不过那题卡常。
首先如果边权不大,那么可以直接二分长度,然后边分治(或点分治)判断长度小于等于 \(k\) 的链的数量。可以先边分治,对每对分治区域的那些半条链先排个序,二分的时候遍历每个分治区域,直接双指针扫,复杂度是 \(O(n \log^2 n)\) 的。
考虑边权为 \(n^{p_i}\),永远不可能进位,于是可以主席树维护哈希值加速权值的比较,一次比较是 \(O(\log n)\) 的。
还有,这次不能直接二分长度了,需要用到随机化二分。这种算法适用于二分对象为对于若干点(不太多),每个点有若干点可以匹配,并且那些点是有序的,存在快速找到 \((i,j)\) 权值的算法,要求权值第 \(k\) 大的点对(本题求的是路径,小芈那题求的是区间)。此时,可以随机一个点对 \(mid\),计算小于等于这个点对的点对数量是否到达 \(k\),并顺便算出来每个点在小于等于 \(mid\) 的前提下最大的另一个点是那个,即为 \(ptr_i\)。根据点对数量是否达到 \(k\) 来调整每个点的范围。(具体见代码)
最后还有一点要注意,在二分了较多次之后,需要选择最后一个 \(mid\) 作为答案,因为此时可能会出现较多权值相同的点对,此时最后一个 \(mid\) 很有可能就是答案,而如果随便从某个点的对应区间中选择第一个点的话容易出错。
复杂度:时间 \(O(n \log^3 n)\),空间 \(O(n \log^2 n)\)
关键(伪)代码:
for (int _ = 1; _ <= 55; ++_) {
随机出一个数 tar 并找到第 tar 个链 记为 (jzp,jzq)
for (int i = 1; i <= htot; ++i) {//计算 ptr[i] 表示第 i 个点最大能匹配第 ptr[i] 个点。
根据单调性继承上一个的 ptr[i] 以保证复杂度
调整 ptr[i] 并计算总的可行链数 cnt
}
if (cnt == K) break;
if (cnt < K) {//小的不够,需要调大
调整每个点能匹配的上下界
调整 tot, K
} else {//可以,尝试调小
调整每个点能匹配的上下界
调整 tot, K
}
}
答案为最后一个 (jzp,jzq)
其实还有一种随机化算法:以原树的链的形式保留链的长度上限 \(R\) 和下限 \(L\)。每次随机一条在 \(L...R\) 中的链 \(mid\)(不合法就再次随机),根据情况让 \(R = mid\) 或 \(L = mid\)。随机较多次之后直接把所有 \(L...R\) 中的链拿出来 sort
一遍。复杂度仍然是一个 \(\log\) 的,但是比较难写。
赶路
给定平面上 \(n\) 个点,保证坐标两两不同且不存在三点贡献。
给定起点 \(s\) ,给定终点 \(t\),要求构造一条从起点到终点,拐点为那 \(n\) 个点,且不存在某两条边相交的折线。
\(n \le 300\)
做法有很多,有一种非常好写的分治算法:
任选一条非起点且非终点的点 \(p\),将点根据在 \(s\to p\) 左边还是右边分成两个点集。假设 \(t\) 在右边,递归地从左边的点中找到一条从 \(s\) 到 \(p\) 的路径,再从右边的点中找到一条从 \(p\) 到 \(t\) 的路径,拼一块即可。不难发现左边的点和右边的点之间不会互相影响。
复杂度:\(O(n^2)\),随机数据下为 \(O(n \log n)\)。
B
给定 \(n\) 个串 \(T_i\),再给定一个长串 \(S\)。\(q\) 次操作:
1 l r str
: 将 \(S\) 的 \(l...r\) 赋值为 \(str\) 的循环,如把zzzzz
赋值为ab
的循环为ababa
。
2 l r
:设 \(c_i\) 为 \(S\) 的 \(l...r\) 中出现了多少次 \(T_i\),则查询 \(\sum_1^n c_i\)。\(1 \le n,|S|,q,\sum |str| \le 10^5\),保证 \(T_i\) 的字典树大小不超过 \(50\)。
\(O(n^2)\) 暴力显然是 AC 自动机板子题。并且可以预先处理出(fail 树上) \(p\) 节点到根的链中有多少个 \(T_i\),这样最后只用扫一遍即可,就不用再求子树和了。
限制和字典树大小有关,所有操作又是区间操作,显然容易想到要用线段树维护 Trie。
对于线段树上每个节点维护 \(p_i,v_i\),分别表示从 Trie 的 \(i\) 开始走会走到 \(p_i\),且 会获得 \(v_i\) 的权值(先开后闭)。随便维护一下。
难点在于如何处理循环的问题。其实算是个套路吧,倍增适合解决这种“循环”问题,因为倍增只需要快速求出那个位置的值即可,这个可以通过取模来解决。环比较大,但是都可以压缩在较少的点上。如果要处理的是“环 / 基环树上走 \(k\) 步”的问题的话,甚至都不需要搞出环就可以实现。
直接把线段树的节点大小开成 \(2^k\) 的形式能把复杂度砍掉个 \(\log\)。
复杂度:\(O(nm\log n)\)
拜神
给定长为 \(n\) 的字符串 \(S\),\(q\) 次询问求 \(l_i...r_i\) 中出现最少两次的子串的最长长度。
\(1 \le n,q \le 10^5\)
虽然是经典题,但是考试的时候还是没想出来/kk
和事情的相似度几乎一模一样,有一点点加强。大致有两种思路:
LCT+树状数组维护分段函数
经典扫描线思想,考虑右端点右移的结果。在 SAM 上的表现为一条到根的链的所有节点的最近的 endpos 那些串可以更新答案。对于最近 endpos 固定的一系列串(长度为 \(1...l\)),收益函数可以视为一段平着的高为 \(l\) 的函数加上一段斜率为 -1 的函数(因为随着左端点的右移可能这些串会不完整)。由于斜率固定,我们只要求 \(kx+b\) 中的 \(b\) 最大即可。于是可以用树状数组前 / 后缀取 max,单点求值。
然后怎么快速找到最近 endpos 固定的串:不难发现更新最近 endpos 类似于 Access
,于是直接上 LCT 即可。复杂度 \(O(n \log^2 n)\)。
后缀树启发式合并+离线扫描线解决二分答案时二维数点
对每组询问二分答案后还是和事情的相似度一样,考虑枚举后缀树(其实是“前缀树”)上的 LCA,然后启发式合并维护相邻两串的位置的“坐标”。询问就是询问矩形 \((L+ans-1...\infty),(0...R)\) 的价值最大的点。然而复杂度看起来很劣,也不好整体二分。不过发现 \((0...R)\) 是固定的,不随二分答案的 \(ans\) 而改变,于是可以对 \(R\) 那一维进行扫描线,树状数组维护 \(L\) 的信息。
这也提示我们离线扫描线解决二维数点不一定真的没法解决二分等看起来“在线”的问题,对于有特殊性质的问题(如一维是固定的)也可以快速解决的。
复杂度还是 \(O(n \log^2 n)\)。
分层图最小生成树
设 \(G_k\) 表示 \(k+1\) 层图,每层 \(n\) 个点,层内没有连边,每相邻两层之间的连边有 \(m\) 条,\((u_i,v_i,w_i)\) 表示第一层的 \(u_i\) 连第二层的 \(v_i\),边权为 \(w_i\),且每相邻两层之间的连边情况都相同。
对 \(k=1...K\),求 \(G_k\) 的最小生成树的边权和。
\(1 \le n,m,K \le 10^5,1 \le w_i \le 30\)
发现 \(w_i\) 非常小,不难想到考虑每条边的贡献。
\(f_i\) 表示将权值为 \(1...i\) 的边加入后生成森林的边数,那么答案就是 \(\sum_i i(f_i - f_{i-1})\)。
考虑求 \(f_i\)。一层一层算,这样也方便一下子算出 \(k=1...K\) 的答案。由于只有相邻层之间会有连边,于是我们只用考虑当前最后一层的连通情况,而这看起来每次变化不大,且连通块会越来越少,看起来很可做的样子。
第一层可以直接 Kruskal,没什么好说的,考虑后面层的变化。如果不考虑上一层的点的连通情况,那么答案和上一层一样。但是如果上一层的 \(u,v\) 已经连通,且当前 \(u,v\) 也恰好连通,那么我们需要从 \(u...v\) 的环中删掉一条边,且以后都会删掉这条边。于是我们在合并了靠后那层的连通块的时候,顺便记一下合并的哪两个点,下一层判断一下,如果在环里,就来个(时间上)后缀减;否则 \(u,v\) (仅考虑下一层的连边)不在同一连通块内,就把它们在下一层(以及以后所有层)中合并上就好(因为以后每层中这两个连通块都是连通状态,这个不难证明)。注意合并的时候由于可能会合并上下一层中靠后的两个连通块,于是还需要继续在下一层中考虑。
实现就让并查集的根节点为连通块内编号最大的点就好, \(u,v\) 用滚动数组维护下。
复杂度:由于每个 \(w\) 来说一个 \(u,v\) 如果在环里,直接后缀减后扔掉它就好;如果不在环里,一定会合并一个连通块,于是复杂度是 \(O(wn\log n)\)。
某道类似 AtCoder 的题
有 \(n\) 个房间,从第 \(i\) 个房间到第 \(i+1\) 个房间需要 \(a_i\) 个小人按住第 \(i\) 个房间右边的按钮,然后两个房间就相互连通,直到 \(a_i\) 个小人不按按钮(即这 \(a_i\) 个小人无法通行)。同理,要从 \(i+1\) 按按钮使得 \(i\) 和 \(i+1\) 通行,需要 \(b_i\) 个人。
可以在每个房间放若干小人,要求不能存在一种流动方案使得第一个房间的人数达到 \(m\)。问最多能放几个人。
\(1 \le n \le 1000,1 \le a_i,b_i,m \le 10000\)
神仙DP题。
考虑人的最优策略,大概就是能往左走尽量往左走,当然也可以汇集一拨人向右走去“救人”去,一直到没有人能往左走,且不可以让一个区间的人都向右走后让右边的某个屋子人左移。
不难发现行走过程是可逆的,不存在“走过去就走不回来了”这一说,于是可以一开始就放成最后状态,让这些小人不能动弹,这样就不用管小人瞎跑的情况了。即一个要求是 \(c_i \le b_i\),另一个要求是不能让一个区间的人都向右走后让右边的某个屋子人左移。
发现第二个要求比较复杂,考虑把第二个要求记在状态里。
从左到右DP,设 \(f(i,j)\) 表示前 \(i\) 个人使劲向右移能移到 \(i\) 共 \(j\) 个小人(注意不是第 \(i\) 个位置放 \(j\) 个小人),前 \(i\) 个位置最多放几个小人。初始状态 \(f(1,i) = i,i < m\)。
考虑 \(f(i,j)\) 的转移去向。
如果 \(j < a_i\) 的话可以在第 \(i+1\) 个位置放人,考虑第 \(i+1\) 个位置放多少个人。可以放小于 \(b_i\) 个人,即 \(f(i,j) + k\to f(i+1,k)\);也可以放 \(b_i\) 个人,但这样的话位置 \(i\) 的那 \(j\) 个人就可以右移了,即 \(f(i,j) + b_i \to f(i + 1, j + b_i)\)。
如果 \(j > a_i\) 的话是不能在第 \(i+1\) 个位置放人的,否则不符合“不能让一个区间的人都向右走后让右边的某个屋子人左移”的要求。根据这 \(j\) 个人过去 \(j-a_i\) 个后能不能再把剩下的 \(a_i\) 个人拉回去,可以分为两种情况:\(f(i,j) \to f(i + 1, j - a_i),j < a_i + b_i\) 和 \(f(i,j) \to f(i + 1, j),j \ge a_i + b_i\)。
最后答案为 \(f(n,j),0 \le j \le \infty\)。
复杂度:大概是 \(O(nm)\)。
逆序对
求长为 \(n\) 的逆序对数为 \(k\) 的排列数。
\(1 \le n,k \le 10^5\)
又是道“经典题”。不过这个题的转化还是挺棒的。
首先考虑DP怎么做。可以将排列从 \(1\) 到 \(n\) 依次插入,第 \(i\) 次可以插入到第 \(0...i-1\) 个数的后面,贡献为 \(1...i-1\)。
转化一下问题,现在有 \(n\) 个变量,第 \(i\) 个变量 \(x_i\) 的范围为 \(0...i-1\),问和为 \(k\) 的方案数。
发现这个问题当没有变量的范围的时候是可以 \(O(1)\) 求的,为 \({k + n - 1 \choose n - 1}\)。于是考虑容斥,设 \(f(i)\) 为恰好有 \(i\) 个超出去的方案数,\(g(i)\) 为钦定 \(i\) 个超出范围的方案数,不难发现这是个二项式反演(子集反演)。
考虑求 \(g(i)\)。不妨直接求 \(g(i,j)\) 表示有 \(i\) 个超出去,且超出去的 "i" 的和为 \(j\) 的方案数。这个问题又可以转化为将 \(j\) 划分为 \(i\) 个互不相同的小于等于 \(n\) 的正整数的方案数。直接整数拆分数搞搞即可。
于是总复杂度为 \(O(n \sqrt n)\)。
修行
求长为 \(n\) 的排列中 \(p_i > p_{i-1}\) 的 \(i\) 的个数恰好为 \(K\) 的排列个数。
\(1 \le n,K \le 10^5\)
似乎是欧拉数。
首先将排列的每一个数看作一个随机变量,最后随机出 \(K\) 个“下降”的概率乘上 \(n!\) 就是答案。
但是这样并不好处理“下降”。一种比较妙的方法是将这些随机变量做一遍前缀和,前缀和数组为 \(S\),这样以后每一位的小数部分可以看作随机的。并且如果出现“下降”,说明产生了进位。于是最后 \(S_n\) 的整数部分即为“下降”。
于是问题转化为了 \(n\) 个 \(0\) 到 \(1\) 的随机变量的和小于等于 \(K\) 的概率,运用 几何概型大概就是在 \(n\) 维空间(以三维为例)中即在 \(1 \times 1 \times 1\) 的正方体中,又在靠着坐标轴的三边长为 \(K\) 的三棱锥中的点的体积(除以 \(1 \times 1 \times 1\) 正方体的体积)。
对于那个三边长为 \(K\) 的三棱锥的体积在 \(n\) 维空间的情况,可以用积分或找规律证明是 \(\frac{K^n}{n!}\)。但是还有 \(0\) 到 \(1\) 的限制。于是可以直接容斥,转化为求 \(k\) 维超出限制的点组成的体积。那么就是选完这 \(k\) 个维度之后让它们先减去 \(1\),转化为 \(n\) 个随机变量的和小于等于 \(K - k\) 的问题,答案为 \(\frac{(K - k)^n}{n!}\)。
复杂度:\(O(n \log n)\)(瓶颈在快速幂)
反色游戏
给定 \(n\) 个点 \(m\) 条边的简单无向图,每个点为黑色或白色。对于一条边,可以反转其两端点的颜色(黑变白,白变黑),也可以不反转。
Subtask1:求有多少种方案,使得所有的点都变为白色。
Subtask2:\(n\) 组询问,每次给定 \(i\),求删掉 \(i\) 号点(\(1 \le i \le n\))后 Subtask1 的答案。
\(1 \le n,m \le 10^5\)
不要拘于一种套路就不去想其它方法了啊qaq
显然有个 \(O(\frac{n^4}{w})\) 的高斯消元解方程算法,但是这个很难继续优化下去。
考虑对于一张连通图,搞出一棵生成树以后,不管点的颜色状况是怎么样的,只要黑点个数为偶数,就可以只用这棵树上的边就可以把所有点全部调整为白色(证明考虑从叶子开始构造)。于是当其它边方案确定后,树边有唯一情况。于是答案为 \(2^{m-n+1}\)。
于是 Subtask1 搞一下有几个连通块直接输出就好了。Subtask2 又是删点又是连通可以想到点双或圆方树,建出圆方树讨论一下就好了。
复杂度 \(O(n + m)\)。
Number of Components
给定长为 \(n\) 的数组 \(a\),如果 \(i<j,a_i < a_j\) 则存在无向边 \((i,j)\)。
\(q\) 次修改,每次将 \(a_p\) 修改成 \(v\),并询问连通块数。
\(1 \le n,q \le 5 \cdot 10^5,1 \le a_i,v \le 10^6\),保证任意时刻 \(a_i\) 互不相同。
可能需要比较高超的观察性质能力和转化能力。
首先要发现如果有边 \((i,j)\),那么 \(i...j\) 种的所有数一定都在它们的连通块内。证明可以讨论中间数和 \(i,j\) 的大小关系。于是一个连通块一定是一段连续的区间。
考虑在连通块的边界统计贡献,发现如果 \(i,i+1\) 之间是边界的话,那么 \(1...i\) 一定大于 \(i+1...n\),即 \(\min_{j=1}^i a_j > \max_{j=i+1}^n a_j\)。方便起见,我们认为 \(a_0\) 和 \(a_1\) 间为边界,即 \(a_0 = \infty\),于是变为 \(\min_{j=1}^i a_j > \max_{j=i+1}^n a_j\)。据此可 \(O(n)\) 回答一次询问。
还是比较难修改。考虑枚举 \(\max_{j=i+1}^n a_j\) 的值 \(w\),那么一个值 \(w\) 可以产生贡献,当且仅当这个值在序列 \(a_i\) 中出现过,且如果把 \(\le w\) 的标为 \(0\),\(>w\) 的标为 \(1\),那么序列一定是 \(1111....110000...00\)。绝对恰好只有一对相邻的 \(0\) 和 \(1\)。
于是我们只用统计只有一对相邻的 \(01\)(也可算上 \(10\),但显然不影响答案) 的 \(w\)。这类似一个统计问题。对于 \(a_i,a_{i+1}\),将 \(\min(a_i,a_{i+1})...\max(a_i,a_{i+1})-1\) 的 \(w\) 的 \(01\) 对数加一,最后查只有一对 \(01\) 的 \(w\) 的对数。又由于 \(w\) 的 \(01\) 对数至少为 \(1\),我们相当于查询全局最小值数量。区间加,全局查合法位置的最小值数量,这可以用线段树来解决。
复杂度:\(O(n \log n)\)
树上GCD
给定 \(n\) 个点的树,对于每个 \(d\) ,求 \(\sum_{u \not= v} [\gcd(dis(u,lca),dis(v,lca)) = d]\)。
\(1 \le n \le 2 \cdot 10^5\)
显然可以容斥,转化为计算 \(g(d)\) 表示 \(gcd\) 为 \(d\) 的倍数的点对数。
根号分治。当 \(d\) 比较小的时候,可以对每个 \(d\) 进行 DP,求出以 \(x\) 为根的子树内深度为 \(d\) 的倍数的点数。当 \(d\) 比较大的时候,可以启发式合并 \(f(x,i)\) 数组表示 \(x\) 子树内深度为 \(i\) 的点数,当两棵子树深度都大于等于 \(i\) 的时候才计算答案。
复杂度:\(O(n \sqrt {n \log n})\)。
质因数个数和
求:
\[\sum_{i=1}^n 2^{\omega(i)} \pmod {10^9 + 7} \]其中 \(\omega(i)\) 表示 \(i\) 的质因数个数。
\(1 \le n \le 10^{12},tl=1s\)
这时 Divcnt2 中的一部分,可以整除分块套整除分块做到 \(O(n^{\frac{2}{3}})\),但是无法通过此题。
考虑换一种思路推式子。
然后是经典莫反:
其中:
直接求 \(f(n)\) 是 \(O(\sqrt n)\) 的,于是暴力的总复杂度为 \(\sum_{i=1}^{\sqrt n} \sqrt {\frac{n}{i^2}} = \sqrt n \sum_{i=1}^{\sqrt n} \frac{1}{i} = \sqrt n \log n\)。可以通过。
最近黑点
有 \(n\) 个点的无权无根树,初始均为黑点。初始版本为 \(0\)。设 \(c\) 为当前版本编号,\(t\) 为之前有过的最大版本编号(初始都为 \(0\))。\(q\) 次操作:
1 x
:在 \(c\) 的基础上新建版本t=t+1
,并将 \(t\) 的点 \(x\) 颜色反转,然后执行c=t
。2 x
:询问版本 \(c\) 中 \(x\) 到 \(x\) 的最近黑点的距离。3 x
:执行 \(c=x\)。保证 \(1 \le c \le t\)\(1 \le n,q \le 10^5\),强制在线。
如果不可持久化,前两问就是个点分治板子题。因为边权均为 \(1\),不用容斥,用 set
或值域线段树维护一下就好,询问就线段树上二分。
可持久化的话,显然可以将值域线段树改成主席树,但是发现我们不能每次把所有的 \(n\) 棵线段树全部更新版本。不过由于更新版本的线段树不是很多,只有 \(O(\log n)\) 个,可以用可持久化数组来维护这 \(n\) 棵线段树的 rt
。
时空复杂度:\(O(n \log^2 n)\)。
排列计数
求有多少长为 \(n\) 的排列,可以在小于等于 \(K\) 次的交换任意相邻两项的操作后使得 \(p_i = i\)。
\(1 \le n \le 10^9,0 \le K \le 3 \cdot 10^3\)
显然可以转化为求 \(n\) 个点有标号元素组成至少 \(n-K\) 个无标号环的方案数。直接上第一类斯特林数搞是 \(O(n^2)\) 的。
注意到大部分的环都是大小为 \(1\) 的自环,于是可以设 \(s_3(n,m)\) 表示 \(n\) 个点组成的 \(m\) 个大小至少为 \(2\) 的环的方案数。类似第一类斯特林数的式子:\(s_3(n,m) = (n-1)s_3(n-1,m) + (n-1)s_3(n-2,m-1)\)。
最终答案为 \(\sum_{k=1}^{K} Ans(n,k) = \sum_{k=1}^K \sum_t {n \choose t} s_3(t,t-k)\)。注意到当 \(i < 2j\) 的时候 \(s_3(i,j)=0\) ,于是 \(t\) 只用枚举到 \(k\) 即可。
总复杂度:\(O(K^2)\)。
Border 的四种求法
给定长为 \(n\) 的字符串 \(S\),\(q\) 次询问子串 \(l_i...r_i\) 的 border 的长度。
\(1 \le n,q \le 2 \cdot 10^5\)
考虑如何用 SAM 来求 border。border 的含义为对于位置 \(i\),存在 \(LCS(i,n) \ge i\) 即为 border。于是可以想到枚举 LCS,然后就变成了 \(l...l+len-1\) 的位置中最靠右的 endpos 位置,这个可以用线段树合并来维护。注意到来自同一棵子树并不影响答案,所以不用容斥。复杂度:\(O(n^2 \log n)\)。
考虑用树链剖分来优化。考虑把轻边下的子树挂到重链下,那么这些轻边中的 endpos 的限制为 \(l \le p \le r,p-len+1 \le l\),在此前提下最大化 \(p\)。这个可以把 \(p\) 当作下标,\(p-len+1\) 当作值,线段树上二分出最靠右的合法的 \(p\) 的位置,来更新答案。挂完后还要对重链做一遍前缀和,方便快速统计。
注意还有可能会有答案在重链的下面部分,因为我们从轻链跳上来的时候可能跳到了一条重链的中间部分,所以还要再维护一种线段树维护子树中的 endpos 集合,跳一次重链就查一次。
复杂度:\(O(n \log^2 n)\)。
精准预测
\(n\) 个人,\(T\) 个时间点。人有两种状态:活,死。规定人不能复活。
有 \(m\) 条限制:
0 t i j
:第 \(i\) 个人在 \(t\) 时死,则第 \(j\) 个人在 \(t+1\) 时死
1 t i j
: 第 \(i\) 个人在 \(t\) 时活,则第 \(j\) 个人在第 \(t\) 时死对于每个人 \(u\),求有多少个人有可能和它一起活到最后。
\(1 \le n \le 50000,1 \le m \le 10^5,1 \le T \le 10^6,Ml=1024MB\)
比较新颖的一种 2-sat 题。
暴力建图是 \(O(nT)\) 个点的,优化掉没用的点后可以做到 \(O(n+m)\) 个点。
显然一定存在可行方案(如一开始全都死),并且观察发现这张图实际上是个 DAG,连 tarjan 都不用。但是这题要求的是有多少个人可能和 \(u\) 一起选”活“。如果转化为“\(u\)活的话有多少人活”,可能会比较麻烦,可以转化为“ \(u\) 活的话会导致多少人死” 并上 一定会死的人数。这是个 DAG 上的可达性统计问题,可以用 bitset
优化到 \(O(\frac{nm}{w})\)。
总复杂度:\(O(\frac{nm}{w})\)
然而会被卡常(卡时卡空)。于是可以分批多次跑可达性统计来解决空间问题。时间问题的话,需要压缩点的个数。有个结论:如果限制为 c t i j
,那么我们只需要新建状态 \((i,t)\),然后直接连向 \(j\) 的 \(t\) 后面第一个状态,因为所有的限制都是让 \(j\) 死,于是 \((j,t)\) 可以和后面的点合并。这样点数可以减半。
Tree Edges XOR
给定 \(n\) 个点的树,边有边权 \(v_{1,i}\)。
每次可以选择一条边 \((x,y)\),使得仅与 \(x\) 或 \(y\) 中一个相连的边的边权异或上这条边的权值。
问是否可能存在某种操作序列,使得最终的边的边权为 \(v_{2,i}\)。
\(1 \le n \le 10^5,1 \le v_{1,i},v_{2,i} \le 10^9\)。保证 \(n\) 为奇数。
很妙的题。
看到操作很奇怪,考虑将边权转化为点权。可以证明,存在一种对点分配点权的方式,使得对于每条边 \((x,y)\),\(w_x \oplus w_y = v_{x,y}\)。显然所有点点权同时异或一个数后仍然合法,于是可以认为根为 \(0\),然后自上而下决定点权。
转化完以后就比较好说了,发现一次操作相当于将两个点权交换,又因为可以证明能够通过有限次交换点权的操作使得点权任意分布。于是只用判 \(v_{1,i}\) 组成的集合异或上某个数 \(x\) 是否能和 \(v_{2,i}\) 组成的集合是否相同即可。又因为 \(n\) 为奇数,可以直接异或一下得到这个 \(x\),然后让 \(v_{1,i}\) 统一异或上这个 \(x\) 后直接判断集合是否相同即可。复杂度 \(O(n \log n)\)。
XOR Tree
给定 \(n\) 个点的树,边有边权 \(w_i\)。
每次可以选择 \(x,y,w\),使得链 \(x...y\) 的边权都异或上 \(w\)。
问最少多少次操作能使得所有边的边权为 \(0\)。
\(1 \le n \le 10^5,0 \le w_i \le 15\)
又是个比较妙的边权放点权上的题。只不过这回让边权等于点权的异或不好做,可以让点权为边权的异或。然后发现一条链的边权异或 \(w\) 就相当于两端点异或 \(w\),最终的目标是所有点权均为 \(0\)。
这样就摆脱了树的结构,转化为了一个贪心问题。显然一次操作可以消掉一个或两个数,并且一次消掉零个并不会减少超过一次的后续操作(或者可以看作先消的别的),于是肯定能消两个就消两个,否则消一个。但是可能会消一个的时候消着消着就发现了两个相同的,这样就又能少一次操作。于是需要 DP。设 \(f(S)\) 表示还剩集合 \(S\) 的最少次数,随便转移一下即可。
复杂度:\(O(n + w^2 2^{w})\)。
图论
\(n\) 个点,点有点权。\(m\) 条线路。共两种线路:
1 k a1 a2 ... ak
:可以双向通行,一次通行路线为这条链的一部分,代价为这部分中的点权最大值。
2 k a1 a2 ... ak
:仅能单向通行,从左向右。一次的代价为这部分的点权最小值。求 \(n\) 到 \(1...n\) 的最短路长度。
\(1 \le n,m \le 10^5, \sum k \le 250000\)
显然有个线段树优化建图的做法:每次找到最值,利用线段树辅助连边,然后递归。边数为 \(O(\sum k \log n)\),难以通过。
正解是个根据特殊情况来优化建图(怎么感觉有点像网络流建图)
第一种建出笛卡尔树,原点向新点连点权,笛卡尔树上向上走花费差值的代价,向下走不花费代价。
第二种因为是最小值,于是我们只需要保证合法即可,最短路算法会自动选最小的走。
总边数为 \(O(\sum t)\)。
几何
给出一个正 n 边形的三角剖分,将其视为以 n 个顶点为点的一张图(点从 1 到 n 逆时针标号,共含 2n-3 条边),求其生成树数量,对 998244353 取模。
\(3 \le n \le 2 \cdot 10^5\)
好像三角剖分题剥最外层三角形是个套路。
对于一条边 \((x,y)\) 设状态 \(f(x,y)\) 表示弧 \(x,y\) 选一些边后 \(x,y\) 最后连通的方案数,类似地设 \(g(x,y)\) 为不连通但所有点要么和 \(x\) 连通,要么和 \(y\) 连通。
考虑剥掉一个外侧三角形,分几类讨论转移即可。
最终只剩下三个点的时候就可以直接大力讨论了。
复杂度 \(O(n)\)。(一条链表实现存边,找二度点用队列)
Raining season
给定 \(n\) 个点的树,边权为 \(a_i \cdot x + b_i\)。
对于 \(x=0...m-1\),求树的直径长度。
\(1 \le n \le 10^5,1 \le a_i \le 10^5,1 \le b_i \le 10^9,1 \le m \le 10^6\)
假设我们能取出所有链的长度 \((a_ix+b_i)\),那么对于 \(x=j\) 的情况,最大值为这些直线组成的半平面交的上凸包的 \(j\) 的位置的坐标。看似需要半平面交,实际上可以转化为一个简单凸包问题:类似斜率优化,\(y=a_ix+b_i\),\(x\) 已知,求最大的 \(y\),那么可以转化为 \(b_i = -a_ix+y\),坐标为 \((a_i,b_i)\),最大化纵截距,然后就是简单凸包了。
现在我们需要整出来所有链的凸包。边分治,对两边分别建凸包,合并发现是两维相加,裸的闵可夫斯基和。最后把那些闵可夫斯基和后的点全拿出来做个凸包,用个指针扫一遍即可得到所有答案。
复杂度:\(O(n \log^2 n)\)。
Contest with Drinks Hard
有 \(n\) 个点,每个点有个代价 \(c_i\)。
可以选若干段,选一段 \(i...j\) 的收益为 \(\sum_{q=1}^{j-i+1}q - \sum_{q = i}^j c_q\)。
要求最大化总收益。
有 \(m\) 次修改,每次在最开始的基础上改变 \(p\) 的代价 \(c_p\) 成 \(x\)。
\(1 \le n,m \le 3 \cdot 10^5,\sum c_i \le 10^{12}\)
很经典的套路了,已经见过两遍了(上一次是zrwc的麻将题)。
首先有一个显然的斜率优化,可以做到 \(O(nm)\)。(这个插入点坐标和询问斜率都递增,还要维护最大值,要用单调栈维护凸包)
显然反过来做也是可以的。于是预处理出 \(f(i)\) 和 \(g(i)\) 表示前缀和后缀的 DP 数组。对于一次修改,分类讨论:
- 不选 \(p\),为 \(f(p-1) + g(p + 1)\)。
- 选 \(p\)。设 \(h(p)\) 为强制选 \(p\) 的最大收益。观察式子发现改变 \(c_p\) 不会改变二次项,只会改变一次项,所以最优区间不变。如果处理出 \(h(p)\),最终答案为 \(h(p) + c_p - x\) 。考虑处理 \(h(p)\)。分治解决,在 \((L,mid,R)\) 中考虑跨越 \(mid\) 的区间对 \(L...R\) 中所有点的影响,这个可以通过对左边建凸包右边扫,和右边建凸包左边扫来解决。
总复杂度:\(O(n \log n + m)\)。
Leftmost Ball
有 \(n\) 种颜色(不为白色)的球,每种颜色的球有 \(k\) 个。
现将这 \(nk\) 个球随机打乱顺序排成一排,然后把每种颜色的第一个球涂成白色。
问最终合法颜色序列的数量。
\(1 \le n,k \le 2000\)
这种数操作后序列的数量的题一般套路是观察操作后序列的性质,找到什么样的序列是可以被达到的。
现考虑弱化版,\(k=1\) 的情况不说,对于 \(k = 2\) 的情况,此时最终的序列可以看作括号序列,白色为左括号,其余颜色为右括号,其数量为长为 \(2n\) 的括号序列的数量(可能还要对每个右括号分配颜色)。
现在考虑 \(k\) 更大的情况。对于一种颜色来说,我们在放第一个球的时候,还要分配好这个颜色的其余球的位置。具体来说,我们直接往最后的 \(n-k\) 个位置上插球。设状态 \(f(i,j)\) 表示前面已经有了 \(i\) 个白球 \(j\) 个其余颜色的球的方案数。考虑现在第一个空位置要插哪种颜色的球。如果是白色,就直接插;否则还要知道这种颜色的其余的 \(k-2\) 个球放到哪里,显然放到剩下随便 \(k-2\) 个空位都可以。
复杂度:\(O(nk)\)
寻宝
一个 \(n \times m\) 的网格,选出包含 \((1,1)\) 的若干点,使得最长哈密顿回路的长度尽量大。两坐标之间的距离定义为曼哈顿距离。
路径上的相邻两点不一定在网格上相邻。
要求构造方案。
\(1 \le n,m \le 1000\)
首先考虑确定一个上界。
显然把所有点都选上不会更劣。
对横纵坐标分别考虑。一个点会在回路中出现两次,每次其坐标会被加或被减。一共有 \(n \times m\) 条边,于是上界为前一半都被加,后一半都被减。
对于 \(n\) 或 \(m\) 为奇数的时候,网格中间会有一排(列)点,这些点左(上)边都要求被减,右(下)边都要求被加,而中间的这排(列)点有一半被减,一半被加就好。
以 \(n,m\) 均为奇数的情况为例。首先可以不停地在左上和右下之间跳,这样能够满足左上和右下的所有点。同理,右上和左下也一样。我们需要一种连接左上右下和右上左下的方法。此时发现中间的那排(列)还没选,于是可以借助中间的那排(列)来跳。
而 \(n,m\) 均为偶数的时候是达不到上界的,所以只能牺牲一下右上左下,把它们的靠中间的两个点的要求改变一下。可以证明,能够达到上界减二。
Lamps on a Circle
\(n\) 个白点排成圆。两人博弈。先手可以选择 \(k\) 个点涂黑,\(k\) 即那 \(k\) 个点均由先手决定。后手可以选择一个起始点 \(s\),从 \(s\) 顺时针向后共 \(k\) 个点涂白。其中 \(k\) 为先手上回选择的数字。先手需要最大化黑点个数,后手需要最小化黑点个数。
要求作为先手和交互库博弈,使得达到最劣情况下的最优局面。
\(1 \le n \le 1000\)
首先确定黑点个数的上界。假设最后的数为 \(k\),那么最后一步想要增加黑点的数量,需要要求涂完后不能有连续的 \(k\) 个黑点,否则就白涂了。感性理解每次选择的 \(k\) 相同。经计算,上界为 \(n-k-\lfloor \frac{n-1}{k} \rfloor\)。构造就是将所有 \(\mod k = 1\) 的点视为特殊点,每次随便选 \(k\) 个非特殊点。
Asterisk Substrings
给定小写字母字符串 \(s\),设 \(t_i\) 为将 \(s\) 的第 \(i\) 个字符改为特殊字符
*
后的字符串。求集合 \(\{ s,t_i\}\) 的本质不同子串个数。
\(1 \le |s| \le 10^5\)
可以将最终的子串分几类,发现最难的还是 \(s*t\) 。可以建出 SAM,枚举 \(s\) 所在节点,那么 \(t\) 即为 endpos + 2 为开头的所有子串。这相当于反串的后缀的前缀,在反串 SAM 上为一条链。那么本质不同的 \(t\) 的个数可以表示为所有 endpos 在反串 SAM 上的链的并。
链并有点像虚树大小,为所有点的深度减去 dfn 相邻点 lca 的深度。可以类似 语言 的做法用线段树合并维护链并。
复杂度 \(O(n \log n)\)。
String Journey
给定字符串 \(S\)。要求从 \(S\) 选出出若干不重叠的段,按顺序排列为 \(t_1,t_2,...,t_k\),要求 \(t_{i+1}\) 是 \(t_i\) 的真子串,即不完全相同的子串。
输出最大的 \(k\)。
\(1 \le |S| \le 5 \cdot 10^5,tl=5s\)
翻转 \(S\) 后变成后一个完全包含前一个。有个显然的结论是, \(t_i\) 序列的长度为 \(1,2,...,k\),每次为前面的子串多一个字符。
设 \(f_i\) 表示以 \(i\) 结尾最多长为多少。转移似乎不好做,但是有个显然的性质是 \(f_{i-1} \ge f_i - 1\)。所以每次最多大 \(1\),从 \(f_{i-1}+1\) 往下依次 check 即可。
check 以 \(i\) 结尾长为 \(l\) 的串是否可行,可以从 SAM 上这个串的节点的子树(含自己)中找到最大的合法的 \(f\),看看有没有达到 \(l-1\)。可以用线段树维护 Parent 树的子树最大值。
注意到“合法”的含义为 endpos 小于等于某个数,而这个数是在不断增大的,所以每次增大的时候直接把 \(f\) 插到那个长为 \(f\) 的节点上。
还是有很多地方值得好好想想的。
复杂度:\(O(n \log n)\)。
good
博弈。
先手可以造一棵 \(n\) 个点的有标号无根树 \(T\),要求每个点度数不超过 \(d\);然后后手选择一条有向链 \(a_1,a_2,...,a_k\);最后先手选择一个二元组 \(op,i(op\in \{1,2\},1 \le i < k)\),\(op=1\) 表示对于 \(a\) 序列,将 \(i+1...k\) 翻转后加上 \(a_i\);\(op=2\) 表示对于 \(a\) 序列,将 \(i+1...k\) 取相反数后加上 \(a_i\)。
如果 \(a\) 序列翻转后递增或递减,就称 \(a\) 序列是好的。
先手会尽量让最后的 \(a\) 序列是好的,后手则会尽量阻止先手让 \(a\) 成为好的。如果有多种决策的结果相同,将随机选一种。
求所有的可能的局面五元组 \((T,a_1,a_k,op,i)\) 的数量。
\(1 \le d < n\le 200\)
很厉害的题,根本一步都推不动啊
比较显然的是先手一定存在必胜策略,随便玩玩就出来了。
首先需要一个比较厉害的发现:第三步操作一定要求链的编号为单峰或单调,且每种合法链的选择方案恰好有两种。所以只用算出所有链都是单峰或单调的树的个数就可以了,最终乘上个 \(2n(n-1)\) 即可。
考虑合法树长什么样子。感性理解就是我们能够拎出来一个点为根节点,使得从根节点出发的所有链都是单调的(递增或递减)。充分性显然。必要性可以考虑这些链分为增链和减链,或者说根据和父边的大小关系分为大点(比父亲大)和小点,那么拎出来后所有子树一定要么全是大点,要么全是小点。换个角度思考,将 \(1\) 作为根节点,那么一定是上面是小点,下面是大点(即大点的子树全是大点),否则出现多峰。并且还有个比较妙的发现,就是不能出现一个大点的父亲是个小点,且这个小点的祖先有一个大点儿子。于是最终合法情况中的大点一定形如一条链下面接个子树,把分界点提出来做根即可。这样就证明了充分性。
直接把这种节点作为根,每棵子树全是大点或全是小点,可以对大点和小点分别 DP,最终合并。不难发现这两种是等价的。对于小点来说,设 \(f(i,j)\) 表示 \(i\) 号点度数为 \(j\) 的方案数。考虑将儿子按编号从大到小枚举,那么最后一棵子树的根节点编号一定是 \(2\),剩下的分配标号即可。这部分可以 \(O(n^3)\) 求出所有 \(f(i,j)\)。
但是会算重,因为一棵树可能会有若干这样的合法点。但是两个合法点之间的点一定是合法点,于是可以直接用点减边容斥做即可。而一条合法边(两端点均为合法点)的答案也是比较好算的。
总复杂度:\(O(n^3)\)
玄学
有 \(n\) 个数,第 \(i\) 个数为 \(a_i\),\(q\) 次操作。共两种:
1 l r a b
:新建一种修改为对 \(l...r\) 的数进行修改,将 \(x\) 变成 \((ax+b) \mod m\)(\(m\) 一开始给定)
2 l r k
:询问仅依次执行第 \(l...r\) 个修改后第 \(k\) 个数的值。强制在线。
\(1 \le n,q \le 10^5\)
注意到这种一次函数是可以复合的,并且复合运算是具有结合律的,于是可以用线段树来维护。由于是对一段区间进行线性变换,可以想到用一种类似 ODT 一样的数据结构来维护。运用二进制分组的思想,用线段树维护操作时间轴,插满了一个节点后合并两个儿子的信息,合并用归并排序。由于不需要支持插入删除,只需要支持合并和快速单点查询,可以把那个 “ODT” 用 vector
来维护,合并归并,查询二分。
复杂度:\(O(n \log n + q \log^2 n)\)
整除
求有多少个正整数 \(x\) 满足 \(\sum_{i=1}^n c_ix^{a_i}\) 能被 \(\sum_{i=0}^{m-1} x^i\) 整除。
如果有无穷个,输出 \(-1\)。
\(1 \le n \le 10^5,1 \le m,a_i \le 10^9,c_i \in \{1,-1\}\)
转化为 \(\frac{x^m-1}{x-1} | \sum_{i=1}^n c_i x^{a_i}\),即 \((x-1)\sum_{i=1}^n c_i x^{a_i} \equiv 0 \pmod {x^m - 1}\)。发现 \(x^\alpha\) 模 \(x^m-1\) 就相当于把 \(x^\alpha\) 模 \(m\)。模完后左边的次数就都不超过 \(m - 1\) 了。
设 \(F(x) = (x-1) \sum_{i=1}^n c_i x^{a_i}\),并将多项式 \(F(x)\) 表示为 \(\sum_{i} f_i x^i\)。如果 \(f_i\) 均为 \(0\),显然有无穷解。否则尝试枚举解 \(x_0\),需要判断 \(\sum_i f_i x_0^i \mod x_0^m - 1\) 是否为 \(0\).
发现 \(x_0^i\) 太大,但是我们可以把那个 \(F(x)\) 看作一个 \(x_0\) 进制数,不断进行进位,注意到可以要把该进到 \(x_0^m\) 的数进到 \(x_0^0\)(因为 \(\mod x_0^m - 1\),所以可以这么做),于是最终可以表示成 \(\sum_i f_i' x_0^i\),其中 \(f_i'\) 均小于 \(x_0\)。
考虑什么时候 \(\sum_i f_i' x_0^i \equiv 0\)。看起来 \(\sum_i f_i'x_0^i\) 不是很像能超过模数 \(x_0^m - 1\) 的,但是算一算最大值最小值发现正好是 \(x_0^m - 1\) 和 \(1 - x_0^m\)。于是最终合法情况只有可能是 \(F(x) = \sum 0x_0^i\) 或 \(F(x) = \sum (x_0 - 1) x_0^i\) 或 \(F(x) = \sum(1 - x_0) x_0^i\)。
这样看来只用枚举 \(1...\max c_i + 1\) 的数作为 \(x_0\) 了,其它数进不了位,也达不到合法情况。
注意到每次进位都会是绝对值的和减小 \(x_0 - 1\),于是特判掉 \(x_0 = 1\),剩下的 \(x_0\) 的操作次数上限是 \(n / x_0\) 次,总操作次数的级别是 \(O(n \log n)\) 的。用 set
配 map
维护一下,还要支持回退。
总复杂度 \(O(n \log^2 n)\)。