一、点分治
1.BZOJ2599 Race
2.BZOJ1468 Tree
3.BZOJ2152 聪聪可可
4.BZOJ5186 Cow at Large
二、点分树
1.BZOJ4012 开店
2.BZOJ3924 幻想乡战略游戏 (坑)
三、点分治序
1.BZOJ3784 树上的路径
四、动态点分治
(坑)
我们知道,求任意图的最大独立集是一类 NP 完全问题,目前还没有准确的多项式算法,但是有许多多项式复杂度的近似算法。
例如,小 C 常用的一种算法是:
小 C 现在想知道,对于给定的一张图,这个算法的正确率,输出答案对 $998244353$ 取模
第一行两个非负整数 $n,m$ 表示给定的图的点数和边数。
接下来 $m$ 行,每行有两个正整数 $(u,v) (u\neq v)$ 描述这张图的一条无向边。
输出正确率,答案对 $998244353$ 取模。
这题比较巧妙的地方就是设计状态。考虑比较朴素的一种方法, $0$ 表示一个点未被选,$1$ 表示一个点选了但没在集合中,$2$ 表示一个点在集合中,这样的状态数就是 $n^3$,我们需要考虑的就是如何优化这种状态。
这里要跳出按照排列的顺序选点这个思维定式。只要选了点 $i$ 加入独立集中,那么与 $i$ 相邻的点在它之后任意时候选都不会产生影响,所以假设有 $s1$ 个点没有考虑,这其中有 $s2$ 个点是与 $i$ 相邻的,那么我们考虑这 $s2$ 个点在排列中的情况数,不难发现这个就是 $A_{s1}^{s2}$。注意我们这里说的是「考虑」而不是选取,也就是说先预先给 $s2$ 个点排好位置,而不是按照排列的顺序依次选了这些点。
所以我们可以这样设计状态,设 $f_{S, i}$ 为考虑了集合 $S$ 且其中独立集大小为 $i$ 的方案数。按照上文提到的考虑方法,可以知道只要是不属于集合 $S$ 的点一定可以加入 $S$ 中的独立集,因为与独立集相邻的点已经在 $S$ 中了。设已经考虑了点集 $S$ ,此时要加入点 $k$,与$k$ 相邻的点集为 $e_k$,还有 $s1$ 个点没有考虑,这其中有 $s2$ 个点是与 $k$ 相邻的,有转移方程
其中 $k\notin S$,$s1=n-|S|-1$,$s2=|e_k\bigcap(e_k\bigcup S)|$。这样转移总复杂度就是 $O(n^22^n)$ 了。
1 | #include <cstdio> |
九条可怜在玩一个很好玩的策略游戏:Slay the Spire,一开始九条可怜的卡组里有 $2n$ 张牌,每张牌上都写着一个数字 $w_i$,一共有两种类型的牌,每种类型各 $n$ 张:
攻击牌:打出后对对方造成等于牌上的数字的伤害。
强化牌:打出后,假设该强化牌上的数字为 $x$,则其他剩下的攻击牌的数字都会乘上 $x$。保证强化牌上的数字都大于 $1$。
现在九条可怜会等概率随机从卡组中抽出 $m$ 张牌,由于费用限制,九条可怜最多打出 $k$ 张牌,假设九条可怜永远都会采取能造成最多伤害的策略,求她期望造成多少伤害。
假设答案为 $ans$,你只需要输出 $ans * \frac{(2n)!}{m!(2n-m)!} \mod 998244353 $
第一行一个正整数 $T$ 表示数据组数 接下来对于每组数据: 第一行三个正整数 $n,m,k$ 第二行 $n$ 个正整数 $w_i$,表示每张强化牌上的数值 第三行 $n$ 个正整数 $w_i$,表示每张攻击牌上的数值
输出 $T$ 行,每行一个非负整数表示每组数据的答案。
首先题目要求的其实是所有 ${2n \choose m}$ 种情况造成的伤害之和,是一个计数问题。
有一个很巧妙的条件是「强化牌上的数字是大于 1 的整数 」我们可以据此得出最优策略是:如果强化牌数量大于 $i$,那么就先用最大的 $k-1$ 张强化牌,再用最大的 $1$ 张攻击牌;否则就是先用掉所有强化牌,再用最大的 $k-i$ 张攻击牌。
先将两种牌从大到小排序,设计状态 $f_{i,j}$ 为用了$i$ 张牌最小的牌为 $w_j$ 所有这种情况的强化倍数之和;$g_{i,j}$ 为用了$i$ 张牌最小的牌为 $w[j]$ 所有这种情况的总伤害之和。它们的转移方程分别为 但是这样还不够,我们还要考虑每一个状态所对应的情况数。设 $F_{i,j}$ 为抽到了 $i$ 张强化牌,并使用了其中 $j$ 张的情况的总强化倍数乘对应情况数;$G_{i,j}$ 为抽到了 $i$ 张攻击牌,并使用了其中 $j$ 张的情况的总伤害乘对应情况数。$F_{i,j},G_{i,j}$ 满足式子 这时候我们最初提到的最优策略就要用到了,我们先枚举 $m$ 张牌的类型,然后根据最优策略算出对应的贡献。因此答案就是
最后是实现的细节,我们先预处理 $f_{i,j}$ 和 $g_{i,j}$,利用前缀和可以做到 $O(n^2)$。然后我们可以在计算答案时对于每一个 $F_{i,j},G_{i,j}$ 用 $O(n)$ 计算出来,这样总复杂度就是 $O(n^2)$ 的。
1 | #include <cstdio> |
1月6日 Educational DP Contest 题目链接:https://atcoder.jp/contests/dp
大部分水题就不讲了,记录几道有趣的题目。
思路: 期望要倒着推!!!
设 $f_{i,j,k}$ 为有 $i$ 个 $3$ 个的寿司碟子,有 $j$ 个 $2$ 个的寿司碟子,有 $k$ 个 $1$ 个的寿司碟子的期望,那么就有 $n-i-j-k$ 个空着的碟子。然后倒着推期望!!!!
这样我们就得到了递推的关系。因为转移的顺序问题,我们不能简单地按照 $i, j, k$ 的顺序 DP。但发现转移是从寿司总数少的状态到寿司总数多到状态,因此我们要按照寿司的总数进行 DP。总时间复杂度 $O(n^3)$。
代码:
1 | #include <cstdio> |
思路: 一个简单的容斥计数,感觉和 DP 关系不大。
枚举女生集合 $S$,计算所有男生和集合中女生配对的方案数,记为 $f[S]$。既然题目要求一一配对的方案,那么如果 $n-|S|$ 为奇数,那么答案就应该减去 $f[S]$,否则就加上 $f[S]$。
代码:
1 | #include <cstdio> |
思路: 一个简单的子集 DP。
枚举所有集合 $S$,记 $f[S]$ 为选取 $S$ 集合的最大答案。先算出把 $S$ 集合作为一整组的分数作为 $f[S]$ 的初值,然后枚举 $S$ 的子集 $T$,得到转移方程 $f[S]=max(f[S], f[T]+f[S xor T])$,直接转移就行。总复杂度 $O(3^nn^2)$ 代码:
1 | #include <cstdio> |
思路: 一个简单的树形 DP 加换根操作,但是有一个很有趣的细节。
设 $f_i$ 为以 $i$ 为根的子树的方案数,那么就有 DP 方程:$f_i=\prod (f_{to}+1)$。考虑换根操作,设 $val_i$ 为节点 $i$ 的父亲所在子树的贡献,那么节点 $i$ 的答案就是 $(val_i+1)*f[i]$。$val$ 的求法一般有两种,第一种是通过式子:$val_{to}=(val_x+1)*f_x/f_{to}$ 得到。这种方法看似很简单,但是其中有除法,对于模意义下的除法表面上看可以用扩展欧几里得求逆元实现。但是,有一些数是没有逆元的,也就是说对于任意除数的除法我们不能够简单地实现。
这时候我们就要考虑第二种求 $val$ 的方法,那就是给子树一个固定的顺序,然后求出子树的前缀和后缀贡献,从而推出 $val_{to}$,虽然这种方法更为复杂,但可以很好地避免除法。
代码:
1 | #include <cstdio> |
思路:
代码:
1 | #include <cstdio> |
设 $T$ 为一棵有根树,我们做如下的定义:
• 设 $a$ 和 $b$ 为 $T$ 中的两个不同节点。如果 $a$ 是 $b$ 的祖先,那么称「$a$ 比 $b$ 不知道高明到哪里去了」。
• 设 $a$ 和 $b$ 为 $T$ 中的两个不同节点。如果 $a$ 与 $b$ 在树上的距离不超过某个给定常数 $x$,那么称「$a$ 与 $b$ 谈笑风生」。
给定一棵 $n$ 个节点的有根树 $T$,节点的编号为 $1∼n$,根节点为 $1$ 号节点。你需要回答 $q$ 个询问,询问给定两个整数 $p $ 和 $k$,问有多少个有序三元组 $(a, b, c)$ 满足:
• $a$,$b$ 和 $c$ 为 $T$ 中三个不同的点,且 $a$ 为 $p$ 号节点;
• $a$ 和 $b$ 都比 $c$ 不知道高明到哪里去了;
• $a$ 和 $b$ 谈笑风生。这里谈笑风生中的常数为给定的 $k$。
输入文件的第一行含有两个正整数 $n$ 和 $q$,分别代表有根树的点数与询问的个数。
接下来 $n−1$ 行,每行描述一条树上的边。每行含有两个整数 $u$ 和 $v$,代表在节点 $u$ 和 $v$ 之间有一条边。
接下来 $q$ 行,每行描述一个操作。第 $i$ 行含有两个整数,分别表示第 $i$ 个询问的 $p$ 和 $k$。
输出 $q$ 行,每行对应一个询问,代表询问的答案。
特别经典的一道题,大部分树上的数据结构都能够在这题使用。除了下面讲到都主席树和线段树合并的做法之外,还可以用树上启发式合并,长链剖分等做法去做。
我们将答案分成两个部分。第一部分是 $b$ 是 $a$ 的祖先,答案就是:
第二部分是 $a$ 是 $b$ 的祖先,答案就是:
于是我们不难想到以 $dep[x]$ 为关键字,$siz[x]-1$ 为权值建立权值线段树。对于每一个询问的答案就是在对应线段树上询问区间 $[dep[x]+1, dep[x]+k]$。实现时就可以用主席树或线段树合并来节省空间。
主席树:
1 | #include <cstdio> |
线段树合并:
1 | #include <cstdio> |