杂题笔记
CF 11D A Simple Task
题意
给定一个 \(n\) 个点 \(m\) 条边的简单无向图,询问里面有多少个简单环。
\(n\leq 19\)
解法
对于每一个环,用唯一确定的方法去标记他。(寻找另一种更容易统计的对象,让这种对象可以唯一对应一个环)
我们可以找到这个环里面编号最小的点,分别从这个点的左侧和右侧把环断开,这样就得到了两条链,这两条链都满足这样一个性质:
链的一个端点的编号是整条链中最小的,并且链的两个端点有一条边(这条边不在链中)相连。
我们现在就可以统计这种链的数目,把结果除以二就是环的数目。
因为数据量很小,考虑状态压缩,设计状态如下:
\(f[S][i]\) 表示包含且只包含 \(S\) 中的节点,结尾为 \(i\) ,开头为 \(\text{lowbit}(S)\) 的链的数目。
然后就可以 \(O(2^n n^2)\) 做掉这一道题了。
总结
计数类的DP可以通过“唯一对应”的方法转换问题,并且这个过程中可能会用到最大值和最小值。
Luogu P3311 数数
题意
给定一些字符串 \(s_i\) ,和一个上限 \(N\) ,求出所有【小于等于 \(N\) 并且其十进制表示中不包含任何 \(s\) 中字符串】的正整数的个数。
解法
这一道题显然和多模式串匹配有关系,我们用AC自动机+数位DP的方法来解决。
这一类问题的状态设计一般非常套路,主要是 \(f[i][j]\) 表示【长度为 \(i\) 】且【在自动机上走最终会走到点 \(j\) 】的字符串的数目。然后再加上其他的一些东西(比如这一道题就加了数位DP特有的 \(\text{lim}=0,1\) 和 \(\text{zro}=0,1\) 这两个维度)
并且这一类问题转移的时候一般使用刷表法。下面所讲的转移在同类型的题里面都能使用。
设计状态 \(f[i][j][\text{lim}=1,0][\text{zro}=1,0]\) 表示满足【长度为 \(i\) 】【在自动机上走最终会走到点 \(j\) 】【将其视作一个数,是/否贴住上限 N 】【是/否全是前导零】的【合法】字符串的数目。
转移比较麻烦,我们用自动机维护一个标记数组 legal[x]
,表示一个在自动机上走到 x 节点的串是否没有任何后缀在 s 集合里面。
得到这个数组之后,我们就会发现,如果 \(f[i][j]\) 是合法的,并且 legal[tr[j][k]] == true
,那么我们就可以从 \(f[i][j]\) 刷到 \(f[i+1][tr[j][k]]\) 。
当然还需要考虑其他的特殊情况,比如说当前的字符串全部都是前导零,那么在做出一次 \(k\neq0\) 的转移的时候,接受转移的状态不是 \(f[i][tr[j][k]]\) 而应该是 \(f[i][tr[0][k]]\) 。
这是为了防止在 \(s\) 中含有
01
这样的模式串的情况下,把形如000001
的串(实际上就是1
)错误判定为一个含有01
的不合法串。
总结
在进行数位DP的时候一定要处理好前导零和贴上限的两种情况。注意特殊处理“第一个不是前导零”的位置。
Luogu P2414 [NOI2011] 阿狸的打字机
题意(简化)
给定 \(n\) 个字符串, \(q\) 次询问第 \(x\) 个在第 \(y\) 个里面出现了多少次。
\(n,q\leq 10^5\)
做法
注意到AC自动机有一个性质,对于任意一个节点 \(u\) :
fail[u] == v
\(\Rightarrow\) \(v\) 所代表的字符串是 \(u\) 所代表的字符串的后缀
把条件补充成充分必要,得到下面的引理:
\(v\) 可以通过在 \(u\) 点不断跳fail得到 \(\Leftrightarrow\) \(v\) 是 \(u\) 的一个后缀
然后我们结合下面这两条:
一个字符串 \(P\) 在 \(T\) 中出现,当且仅当 \(P\) 是 \(T\) 的一个前缀的后缀;
在AC自动机的原始Trie上, 任意一个字符串 \(T\) 的前缀都唯一对应 根节点到 \(T\) 所对应节点的路径上的 一个节点;
就可以得到判定AC自动机中两个字符串包含次数的引理,如下( \(S,T\) 是AC自动机中的两个字符串):
\(S\) 在 \(T\) 中出现了 \(n\) 次
当且仅当
原始Trie树上,从根节点到 \(T\) 节点的路径中,有 \(n\) 个节点位于fail树上 \(S\) 点的子树内
然后问题就变成了树上单点加减一&子树查询,可以用树状数组维护fail树的dfn序列来实现。
为了优化加一减一的过程,采用dfs原始Trie树的方法,每次进入一个节点时+1,离开时-1,并同时解决离线下来的询问中所有 \(y\) 为当前字符串的那些。
总结
一定要注意AC自动机的节点数是 \(tot+1\) ,树状数组的上限开大点!
CF 986E Prince's Problem
题意
Luogu P5664 Emiya家今天的饭
题意
给定一个 \(n\) 行 \(m\) 列的矩阵 \(A\) ,矩阵的每一个位置都有 \(A_{i,j}\) 个互不相同的物品。
要求从每一行中选出至多一个物品,并且保证每一列中选出物品的个数不超过选出物品总数的一半(下取整),求一共有多少种方案。
做法
显然是一道DP题,先来思考如何设计状态。
看守
四维空间,曼哈顿距离最小。
按照其中一维排序,从前往后依次扫描,这样解决了其中一维。
考虑在三维空间中选取一个“坐标原点”,那么在立方体的八个角中,总有一个点,当它作为原点时,答案就是最简单的【A与原点的曼哈顿距离】减去【B与原点的曼哈顿距离】加【A的时间减去B的时间(第四维坐标)】。
所以对于八个角维护这种东西就可以了。
注意八个角坐标轴的指向是不一样的。
CF 1458D Flip And Reverse
神题
难度:MEDIUM*(思路极难得到;代码短;细节少)
Tag:建图、结论证明、交错序列、欧拉路径
如果题目中出现“01序列”,可以考虑把所有零改为负一,对新序列进行讨论。
然后求个前缀和,对前缀和序列建图。
建图方案中,一条“边”一般表示一种“合并”或者“后继”的关系,这题也是如此,用一条边表示某个值的后继是某个值,那么前缀和序列就可以表示为这张图的一条欧拉路径。
为什么?因为你要遍历所有的“边”,也就是“后继”关系,并且两个后继关系要保证前后可以接起来。这样得到的一定是欧拉路径。
可以进一步证明,在这种操作方案下,一条欧拉路径也一定反过来对应序列经过几次操作后的一种可能形式。
然后通过交错序列中“-1,1数量相等的连续段”两端的前缀和相等的性质,推导出一次操作相当于把一个环反向,我们就可以把所有有向边改为无向边,并DFS字典序最小的欧拉路。
CF 1266H Red-Blue Graph
难度:HELL(这是怎么想到的)
在三个不同的地方看到了同一道题,是时候写写了。
已加入未来五十年必做计划
观察到一个人第奇数次进入一个点一定走初始边(红边),第偶数次进入时一定走剩余边(蓝边)。
CF 1621G Weighted Increasing Subsequences
难度:MEDIUM
Tag:DP、标值互换、计数
观察到两个性质:
- 对于每个IS,使他有贡献的 \(a_x\) 一定是一个后缀最大值。
- 对于每个IS,它产生贡献的部分一定是这个IS的一个前缀。
既然是计数,那么考虑每一个点产生的贡献如何计算。
先把所有的后缀最大值编号,从后到前分别为 \(a_{x_1},a_{x_2},\cdots,a_{x_K}\) . 然后我们找到第一个大于 \(a_i\) 的后缀最大值 \(a_{x_y}\),可以发现一个 IS 中 \(a_i\) 产生贡献的充要条件是【这个 IS 不包含 \(a_{x_y}\)】。
为什么?首先观察到一个包含 \(a_i\) 的 IS 不可能结束在 \(a_{x_y}\) 之后,因为这个 \(a_{x_y}\) 后面的数一定都比 \(a_i\) 要小。
然后,IS的结尾只剩两种情况:是 \(a_{x_y}\),不是 \(a_{x_y}\) . 显然只有后者会产生贡献。
我们发现接下来要进行计数的是【固定结束位置的IS数目】和【固定开头、固定结束位置不是某个位置的IS数目】。
后者依然不好计数,容斥一下变成【固定开头的IS数目】减去【固定开头结尾的IS数目】。
然后发现,\(a_i\in[a_{x_{y-1}},a_{x_{y}}]\) . 也就是说,以 \(a_i\) 开头,\(a_{x_y}\) 结尾的IS的所有值一定都在 \([a_{x_{y-1}},a_{x_y}]\) 这个区间里面。
所以,我们可以把值域分段,每个段里面IS的结束点都是固定的,这样就可以扫描左端点依次计算了,总时间复杂度顺利降到 \(O(n\log n)\)
这一段理解不了的可以把二维图画出来(横轴为下标,纵轴为值域),每个点代表原序列的一个位置,然后看看分割值域的效果。
3200不过尔尔嘛
BZOJ 4634 转啊转啊转
难度:MEDIUM
Tag:对偶、计算几何
先来看看这个问题:平面中有若干个向量,选出 \(m\) 个,要求它们的和距离原点最远。
假设你有一台阿列夫计算机,可以枚举 \([0,2\pi)\) 中的每一个角度,那么现在可以怎么做?
枚举一个单位向量 \(\vec e\),然后把平面上的所有向量全部投影到 \(\vec e\) 所在的这一条直线上(也就是求点积),然后从这些点积里面选出最大的 \(m\) 个即可。
这为什么是正确的?
首先根据高中数学,我们把点积相加等价于先把这些向量相加,再求它和 \(\vec e\) 的点积。而假如我们的答案位于点 \(A(x,y)\),那么当 \(\vec e\) 与 \(\vec{OA}\) 共线时我们求的点积就是答案本身。
不过我们毕竟没有阿列夫计算机,但是考虑到 \(\vec e\) 所在的直线分割点集的方案一共有 \(n\) 种,那么我们先 \(O(n)\) 遍历一下,再对于最后的答案可能存在的区间细化枚举,也可以达到同样的效果。
据说这种思路来自于某种对偶。
对偶,即对于一个要优化(寻找最小值)的函数 \(f\),找到另一个函数 \(g\),保证 \(g\) 函数恒不大于 \(f\),并且仅在 \(f\) 的最小值处取等。
然后考虑一下这一道题该怎么做。
这个旋转 \(90^\circ\) 的操作让我们想起了复数。所以我们把整个坐标平面翻转,用一个复数 \(z\) 代表目前飞船的位置,那么绕着一个点 \(z_k\) 旋转 \(90^\circ\) 就相当于
即
这意味着什么呢?我们把所有经过的点从 \(0\sim m-1\) 编号,最后一个被操作的点为 \(0\) 号。可以发现编号模 \(4\) 余 \(0\) 的点都是先乘上一个 \((1-i)\) 的系数再对加进答案里面的。
同理,编号模 \(4\) 余 \(1\) 的点系数为 \((1+i)\),余 \(2\) 的点对应系数 \((-1+i)\),余 \(3\) 的点对应系数 \((-1-i)\) .
那么可以把所有的点按照操作的时间模 \(4\) 的结果分成 \(4\) 类,每一类的情况就是刚刚的向量相加问题,只不过可以重复。
然后我们就可以把所有的点复制四份(其实只需要拆成两份就够了),沿用刚刚的方法了。
具体来说,每次找到最大、最小的若干个点积即可。
复平面中,\(\vec x\cdot \vec y = x\bar y\)
[集训队作业2018] 串串划分
这个限制看起来非常复杂,尝试化简。
发现限制等价于:存在一个循环子串,满足它的两边刚好各被切一刀,并且中间的分割位置全部位于循环节之间。
我们希望给每一个子串分配一个“容斥系数”,一种划分方案的权值就是其划分出的子串的“容斥系数”之积。并且保证直接把权值求和就能得到方案数。
我们发现,这道题中的限制本质上是限制了“一个串中不能出现某一类的特殊结构”。
那么把所有
[NOI2019 Day1 T2] 机器人
简明题意:构造序列,要求满足:
- \(a_i\) 在区间 \([A_i,B_i]\) 中。
- 对于每个数,【它与【左边第一个大于他的】的距离】与【它和【右边第一个大于他】的距离】之差不超过二。(没有的话取序列端点)
求有多少序列满足要求。
考虑最值分治,一个区间的最大值两边的方案互不影响,左边走不到右边,右边走不到左边。可以当做两个子问题。为了便于讨论,如果有多个最大值,取最右侧的。
设计DP状态 \(f[l,r,m]\) 表示 \([l,r]\) 这个区间的最大值为 \(m\) 时的方案总数。
那么状态转移方程可以写成:
打表发现,被访问到的区间 \([l,r]\) 数量比较少,只有 \(2000\) 个左右。
现在限制时间复杂度的就是对第三维的处理了。我们把DP状态视作一些函数 \(f_{l,r}(x)\),那么转移方程就是:
假如把 \(A_i,B_i\) 离散化,那么可以根据归纳法证明,每一个定义域区间里面,任何一个函数 \(f_{l,r}(x)\) 都是一个多项式。即,每一个 \(f_{l,r}(x)\) 是一个分段的多项式函数
如何证明?先引入一族函数 \(F_{l,r}(x) = \sum_{k\leq x}f_{l,r}(x)\),一会会用到。
首先看边界条件,对于一个区间里面的 \(x\),\(f_{l,l}(x) = 1\) 或 \(f_{l,l}(x) = 0\) . 这显然是一个多项式。
然后尝试证明,所有的 \(F_{l,r}\) 均为多项式,且其次数均不会高于其对应的 \(f_{l,r}(x)\) 的次数 \(+1\) .
观察到 \(F_{l,r}(x)\) 可以写成如下的形式:
由于 \(f_{l,r}(x)\) 是分段函数,所以系数中途会变化。
改为按列求和,
可以发现每一个块里面都可以提取公因子,剩下的是一个自然数幂和的形式。
而我们知道,自然数 \(n\) 次幂和可以写成一个 \(n+1\) 次的多项式形式。因此,\(F_{l,r}(x)\) 的次数为 \(k+1\) .
把所有信息结合起来,当区间长度为 \(1\) 时,\(f_{l,r}(x)\) 的次数为 \(0\),\(F_{l,r}(x)\) 的次数为 \(1\),等于区间长度。
并且在每次转移中,若转移式右侧两个相乘的 \(F\) 的次数分别为其区间长度,那么可以得到转移式左侧的 \(f\) 次数为区间长度 \(-1\) . 完成归纳。
这样我们可以得到,任意时刻的 \(F\) 的次数都不会超过 \(n\) . 那么只需要保存前 \(n+1\) 个点值,就可以通过拉格朗日插值还原出这个分段区间里面的最后一个函数值了。
流程大概是从小到大枚举分段,然后转移的同时借助之前算出来的最后一个值,计算这个段的 \(F_{l,r}\),最后插值插出来区间中的最后一个值备用。