8.5~8.16 做题笔记

记录了做到的一些不错的题,也有模板和知识点。


CF1548C

题意

\(q\) 个询问,每次给定 \(x\),求 \(\sum\limits_{i=1}^N\dbinom{3i}{x}\)\(1\leq N\leq 10^6\)

做法一

\(f_{x,k}=\sum\limits_{i=0}^{N-1}\dbinom{3i+k}{x}\),则有

  • \(f_{x,0}+f_{x,1}+f_{x,2}=\dbinom{3N}{x+1}\)
  • \(f_{x,1}=f_{x,0}+f_{x-1,0}\)
  • \(f_{x,2}=f_{x,1}+f_{x-1,1}\)

递推就能做了。

用到了上指标求和公式(好像叫什么 Hockey Stick Identity(?):

\[\sum\limits_{0\le k\le n}\binom{k}{m}=\binom{n+1}{m+1} \]

做法二

\[\begin{aligned} \ f_x &= \sum\limits_{i=1}^N\dbinom{3i}{x}\\ &= \sum\limits_{i=1}^N\left(\dbinom{3i-3}{x}+3\dbinom{3i-3}{x-1}+3\dbinom{3i-3}{x-2}+\dbinom{3i-3}{x-3} \right)\\ &=f_x-\dbinom{3N}{x}+3f_{x-1}-3\dbinom{3N}{x-1}+3f_{x-2}-3\dbinom{3N}{x-2}+f_{x-3}-\dbinom{3N}{x-3}\\ &=f_x+3f_{x-1}+3f_{x-2}+f_{x-3}-\dbinom{3N+3}{x} \end{aligned} \]

\(x-3\) 替换成 \(x\) 就得到递推式

\[f_x=\dbinom{3N+3}{x+3}-3f_{x+1}-3f_{x+2} \]

比方法一的简洁些。


斐波那契数列通项公式

\[F_n=\dfrac{1}{\sqrt{5}}(\alpha^n-\beta^n) \]

其中 \(\alpha=\dfrac{1+\sqrt{5}}{2}, \beta=\dfrac{1-\sqrt{5}}{2}\)

可以扩域做,即把 \(a+b\sqrt{5},(a,b\in\mathbb{Q})\) 看作广义的“复数”进行运算。


Codeforces Round #737 (Div. 2)

赛时比较菜,就切了 ABC,D 就是线段树优化 dp,当时有了思路,但没时间写了。E 还是挺不错的,但据说交互库水了点。


长链剖分

树上 k 级祖先

各种做法花里胡哨,这里说下长剖的做法( \(O(n\log n)-O(1)\) )。

预处理:

  1. 对树进行长链剖分,记录每个点所在链的顶点和深度

  2. 树上倍增求出每个点的 \(2^n\) 级祖先

  3. 对于每条链,如果其长度为 \(len\),那么在顶点处记录顶点向上的 \(len\) 个祖先和向下的 \(len\) 个链上的儿子

  4. \(i\in[1, n]\) 求出在二进制下的最高位 \(h_i\)

对于每次询问 \(x\)\(k\) 级祖先:

  1. 利用倍增数组先将 \(x\) 跳到 \(x\)\(2^{h_k}\) 级祖先,设剩下还有 \(k^{\prime}\) 级,显然 \(k^{\prime} < 2^{h_k}\),因此此时 \(x\) 所在的长链长度一定 \(\ge 2^{h_k} > k^{\prime}\)
  2. 由于长链长度 \(> k^{\prime}\),因此可以先将 \(x\) 跳到 \(x\) 所在链的顶点,若之后剩下的级数为正,则利用向上的数组求出答案,否则利用向下的数组求出答案。

(摘自https://www.luogu.com.cn/blog/xht37/solution-p5903

HOT-Hotels

经典问题,求树上两两距离相等的三元组个数。

\(f_{x,j}\) 表示 \(x\) 的子树中到 \(x\) 距离为 \(j\) 的节点数,转移比较简单:

\[f_{x,j}=\sum\limits_{y\in son(x)} f_{y,j-1} \]

再设状态 \(g_{x,j}\) 表示在 \(x\) 的子树中选两个点,它们到其 lca 的距离为 \(d\) 且 lca 到 \(u\) 的距离为 \(d-j\) 的方案数。

转移:

\[g_{x,j}=\sum\limits_{y\in son(x)}\left(g_{x,j+1}+f_{x,j}^{\prime}\times f_{y,j-1}\right) \]

这里的 \(f_x^{\prime}\) 表示 \(f_x\)\(y\) 之前的儿子更新过的值。

答案的统计则要分两种情况,第一种情况,三个点都在同一个子树内,大概长这样:(图片有空再补)

对答案的贡献为

\[\sum\limits_{y\in son(x)}\sum\limits_j\left(g_{x,j}^{\prime}\times f_{y,j-1}+f_{x,j}^{\prime}\times g_{y,j+1}\right) \]

第二种情况,三个点有两个在子树内,另一个为祖先节点,对答案的贡献就是 \(g_{x,0}\)

至此我们就有了 \(O(n^2)\) 的解法。

考虑长链剖分。记 \(x\) 的重儿子为 \(son_x\),我们发现 \(x\) 的信息可以直接从 \(son_x\) 继承来,即 \(f_{x,j}=f_{son_x,j-1},g_{x,j}=g_{son_x,j+1}\),这个可以用指针 \(O(1)\) 实现。对于 \(x\) 的轻儿子 \(y\),我们在 \([0,dep_y]\) 中枚举 \(j\) 转移即可。这样整个过程的复杂度就是所有长链的长度和,时空复杂度均为 \(O(n)\)

CCPC-Final 2019 G

题意

给定一棵 \(n\) 个节点树,\(1\) 号节点有一个标记,两个人轮流移动这个标记到另一个节点,每次移动的距离必须严格比上一次对手移动的距离长。求有多少个包含 \(1\) 节点的联通子图,使得这个游戏后手必胜。

\(1\leq n\leq 2\times 10^5\)

做法

博弈就是纸老虎,不难看出,其实就是求有多少个子图使得 \(1\) 节点是直径中点。

先设计这个恶心的 dp。

\(f_{x,j}\) 表示以 \(x\) 为根且最大深度为 \(j\) 的子图方案数。

转移:

\[f_{x,j}=\left(\sum\limits_{k\leq j}f_{x,k}^{\prime}\right)\times f_{y,j-1}+f_{x,j}^{\prime}\times\sum\limits_{k<j}f_{y,k} \]

其中 \(y\in son(x)\)\(f^{\prime}\) 表示处理到当前儿子之前 \(f\) 的值,这是一个滚动的过程,下文的 \(g^{\prime}\) 同理。

考虑在 \(1\) 节点周围统计答案,再设计一个更恶心的 dp。

  • \(g_{j,0}\) 表示以 \(1\) 为根且最大深度小于 \(j\) 的子图方案;

  • \(g_{j,1}\) 表示以 \(1\) 为根且仅有一个儿子中的最大深度为 \(j\) 的子图方案;

  • \(g_{j,2}\) 表示以 \(1\) 为根且有两个或两个以上儿子中的最大深度为 \(j\) 的子图方案。

转移:

\[g_{j,2}=g_{j,1}^{\prime}\times f_{y,j-1}+g_{j,2}^{\prime}\times \sum\limits_{k<j}f_{y,k}\\ g_{j,1}=g_{j,0}^{\prime}\times f_{y,j-1}+g_{j,1}\times \sum\limits_{k<j-1}f_{y,k}\\ g_{j,0}=g_{j,0}^{\prime}\times \sum\limits_{k<j-1}f_{y,k} \]

观察这个转移,我们发现所有转移都和深度有关,于是想到用长剖优化。处理当前节点 \(x\) 时,继承 \(x\) 重儿子的信息,再枚举每个轻儿子所在长链,更新 \(x\) 的信息。式子最后面的那个西格玛,枚举 \(j\) 的时候维护一个前缀和就好了。

不过还有一个问题,根据转移,处理 \(x\) 时所有的 \(f_x\) 都会被轻儿子更新,但我们又不能枚举到 \(dep_x\)(否则复杂度有变成 \(n^2\) 了),不过再仔细观察,我们发现当 \(j>dep_y\) 时,后面的那个西格玛就是一个定值,这就相当于给 \(dep_y<j\leq dep_x\)\(f_{x,j}\) 都乘上一个定值,所以打个懒标记就好了,用到的时候再下传懒标记,这样就保证了总的复杂度 \(O(n)\)

\(g\) 的转移也就同理了,打懒标记即可。

提交记录


luogu P1273

\(O(n^2)\) 的树上背包

首先把所有节点按后续遍历重新标号

\(f_{i,j}\) 为前 \(i\) 个点,背包容量为 \(j\) 的最大价值。设 \(siz_i\) 为以 \(i\) 为跟的子树大小。那么 \(f_{i,j}\) 就会从 \(f_{i-1,j-1}\)\(f_{i-siz_i,j}\) 转移过来。

loj 也有这个模板题 https://loj.ac/p/160


换根 dp

CF708C Centroids

题意

给定一颗树,你可以删去一条边,再加入一条边,保证改动后还是一棵树。

求每个点是否可以通过改动成为这颗树的重心。

要判断 \(x\) 是否能成为重心,我们就是要在 \(x\) 的大于 \(n/2\) 的子树中(\(x\) 为根节点)摘下来不超过 \(n/2\) 的最大的子树,接在 \(x\) 上,再判断这个子树剩下部分是否不大于 \(n/2\)

我们需要 dp 求出一个 \(f_x\) 表示在 \(x\) 的子树中不超过 \(n/2\) 的最大子树。

转移大概就是

\[f_x=\begin{cases} \max\{siz_y\}&siz_y\leq n/2\\ \max\{f_y\}&\text{otherwise}\\ \end{cases} \]

不过,具体求 \(f\) 的时候,我们不仅要求出最大值,还要求出一个次大值,这样当我们从 \(x\) 换根到 \(y\) 的时候,如果 \(x\) 的最优决策在 \(y\) 的子树中,我们就要用次大值来更新 \(y\)

提交记录

这个同时记录最大值和次大值的方法应该是换根 dp 的一种常见思路,下面两道题都是如此。(感觉这几道换根写起来细节都挺多的)

P6419 [COCI2014-2015#1] Kamp

P3647 [APIO2014] 连珠线

P3647 这个题还是不错的,但可能换根做多了就比较套路了,我做这题调了一整个下午


P4292 [WC2010]重建计划

看到这个式子一定是分数规划,二分之后就是求树上包含边数在 \(L\)\(R\) 之间的权值和最大的路径。

一个很简单的 dp 就是 \(f_{x,j}\) 表示以 \(x\) 为根子树中以 \(x\) 为一个端点的长度为 \(j\) 的路径的最大权值,然后就有

\[f_{x,j}=\max\limits_{y\in son(x)}\{f_{y,j-1}+w(x,y)\} \]

这个转移长剖就完了,不过统计答案时还有一个边数在 \(L\)\(R\) 之间的限制,这是一个区间查询,所以想到用线段树维护长链。dp 过程中,把 \(x\) 的轻儿子信息转移到 \(x\) 时顺便统计经过 \(x\) 的路径对答案的贡献,然后这题就做完了。复杂度 \(O(n\log^2n)\)

这个题看似复杂,但每一步都非常清晰,不可能做不出来。


矩阵加速 dp

P2579 [ZJOI2005] 沼泽鳄鱼

注意到 \(2,3,4\) 的最小公倍数为 \(12\),计算 \(12\) 个转移矩阵,乘起来之后求一个 \(\left\lfloor \frac{k}{12}\right\rfloor\) 次幂,在乘上前 \(k\bmod 12\) 个矩阵就是总的转移矩阵

P3216 [HNOI2011] 数学作业

状态转移方程:

\[f_i=f_{i-1}+10^k+i \]

其中 \(k=\left\lfloor\lg i\right\rfloor +1\)

矩阵加速:

\[\begin{bmatrix} f_i\\i\\1 \end{bmatrix} = \begin{bmatrix} 10^k &1 &1\\ 0 &1 &1\\ 0 &0 &1 \end{bmatrix} \begin{bmatrix} f_i-1\\i-1\\1 \end{bmatrix} \]

P2886 [USACO07NOV] Cow Relays G

广义矩阵乘法 \(C_{i,j}=\min\{A_{i,k}+B_{k,j}\}\)\(\max\) 也一样)依然满足结合率可以做快速幂,本题就是矩阵加速 floyd。

P3502 [POI2010] CHO-Hamsters

一个串接到另一个串后面多出来长 \(l\) 的一截,看成连了一条长 \(l\) 的边,这个过程用到 KMP。

转化成图论模型后就与上题类似了。


P4095 [HEOI2013]Eden 的新背包问题

奇妙的 cdq 分治+多重背包

不选 \(i\) 物品的答案的贡献是由 \(1\sim i-1\)\(i+1\sim n\) 的物品产生的,所以就可以 cdq 求两边对 \(i\) 的贡献,具体地,

  • 先把 \((mid,r]\) 的物品加入背包
  • 递归 \([l,mid]\)
  • 还原(去掉 \((mid,r]\) 的物品)
  • \([l,mid]\) 的物品加入背包
  • 递归 \((mid,r]\)

复杂度就是 \(O(nV\log n)\)\(V\) 是背包容量。

附:单调队列优化多重背包代码

void add(int w,int v,int s){	//加入重 w,价值为 v 的物品,数量为 s
    for(int i=0;i<w;i++){
        deque<pair<int,int> > q;
        q.push_back(make_pair(0,f[i]));
        for(int j=1;j*w+i<=m;j++){
            while(!q.empty()&&q.back().val<=f[j*w+i]-j*v) q.pop_back();
            q.push_back(make_pair(j,f[j*w+i]-j*v));
            while(!q.empty()&&q.front().id<j-s) q.pop_front();
            f[j*w+i]=max(f[j*w+i],q.front().val+j*v);
        }
    }
}

单调队列优化 dp & 斜率优化 dp

P3195 [HNOI2008]玩具装箱

CF311B Cats Transport

P2569 [SCOI2010]股票交易

CF939F Cutlet


CF1559 D2. Mocha and Diana (Hard Version)

题意

给你两个森林,每次你可以选择两个点 \(u,v\) 在两个森林的 \(u,v\) 节点之间都加一条边,并且要保证两个森林加完边还是森林。你需要加尽量多的边并输出方案。

解题思路

官方题解不说了,也是一个挺不错的做法,用到 set 启发式合并,复杂度 \(O(n\log^2n)\)

在题解下面看到一个做法

先枚举 \(2\)\(n\) 的节点,能和 \(1\) 连边的就都连上,这之后所有的节点都至少在一个森林里与 \(1\) 联通。

然后我们把所有在第一个森林里和 \(1\) 联通的点压入栈 \(s_1\),在第二个森林里和 \(1\) 联通的点压入栈 \(s_2\)

然后不断进行一下操作:

  • 如果 \(s_1\) 的栈顶在两个森林里都和 \(1\) 联通,把它弹出;
  • 如果 \(s_2\) 的栈顶在两个森林里都和 \(1\) 联通,把它弹出;
  • 否则,把 \(s1\)\(s2\) 的栈顶之间连边。

总复杂度几乎是 \(O(n)\) 的,只有并查集的复杂度。

按这个算法连出的边显然都是合法的,而连完之后一定有一个森林中所有点都与 \(1\) 连通(只有一个连通块),这样也保证了连出的边是最多的。

CF1559E. Mocha and Stars

题意

求有多少长度为 \(n\) 的序列 \(a\),满足

  • \(a_i\in [l_i,r_i]\cap \mathbb{Z}\)
  • \(\sum\limits_{i=1}^na_i\leq m\)
  • \(\gcd(a_1,a_2,\dots,a_n)=1\)

\(2\leq n\leq50\)\(1\leq m\leq 10^5\)\(1\leq l_i\leq r_i\leq m\)

解题思路

赛时看群里大家都说 E 很 trivial,然而我愣是一点都不会。/qd

莫反之后就是求

\[\sum\limits_{d=1}^m\sum\limits_{d|\gcd(a1,a2,\dots,a_n)}\mu(d) \]

考虑每个 \(\mu(d)\) 的贡献,把 \(a_i\) 都除以 \(d\)。那就是,对于每个 \(d\),求有多少个序列满足 \(a_i\in\left[\left\lceil l_i/d\right\rceil ,\left\lfloor r_i/d\right\rfloor\right]\cap\mathbb{Z}\)\(\sum a_i\leq m/d\)

这个直接就可以 dp 了,dp 过程用前缀和优化,单次复杂度 \(O(nm/d)\),总复杂度就是 \(O(nm\log m)\)

P4027 [NOI2007] 货币兑换

\(f_i\) 为第 \(i\) 天可以得到的最多钱数,然后列个方程解出第 \(j\) 天花钱买到的两种金券数 \(x_i=\dfrac{f_jr_j}{a_jr_j+b_j},y_i=\dfrac{f_j}{a_jr_j+b_j}\)

状态转移方程就来了:\(f_i=\max\{a_ix_j+b_iy_j\}\)

这个东西如果斜率优化,只能动态维护一个凸包。我记得寒假集训有一个这种斜优题,因为当时不会 cdq 也不会李超线段树,就硬写 Splay,从中午调到半夜没调出来,教练还问我为啥一道题也没补。。。此后我对 Splay 维护凸包就产生了心里阴影。

当然用李超线段树就好多了

变个形:\(f_i=b_i\times \max\{x_j\times\dfrac{a_i}{b_i}+y_j\}\)

\(x_j,y_j\) 看成斜率和截距,李超线段树即可,记得离散化。

我这个代码和题解高度相似(

https://www.luogu.com.cn/record/56036585

P4655 [CEOI2017]Building Bridges

也是裸的斜优,cdq 或李超线段树都行。


luogu P4719 【模板】"动态 DP"&动态树分治

单点修改的树上最大权独立集

经典的没有上司的舞会解法,设 \(f_{x, 0}\) 表示不选择节点 \(x\) 时,以 \(x\) 为根的子树的最大权独立集;\(f_{x, 1}\) 表示选择节点 \(x\) 时,以 \(x\) 为根的子树的最大权独立集。

然后

\[f_{x,0}=\sum\limits_{y\in son(x)}\max(f_{y,0},f_{y,1})\\ f_{x,1}=a_i+\sum\limits_{y\in son(x)}f_{y,0} \]

当然过不了带修的,因为每次修改一整条链上的点的 \(f\) 值都发生了变化,最坏是 \(O(mn)\) 的,然而据说过了这道题

动态 dp

再考虑设一个 \(g_{x,0}\) 表示 \(x\) 的所有轻儿子中都可选可不选的最大独立集,\(g_{x,1}\) 表示所有轻儿子都不选的最大独立集。dp 就变成了

\[f_{x,0}=g_{x,0}+\max(f_{y,0},f_{y,1})\\ f_{x,1}=g_{x,1}+f_{y,0} \]

这样以后,我们就可以把转移写成矩乘的形式了!

\[\begin{bmatrix} f_{x,0}\\f_{x,1} \end{bmatrix}=\begin{bmatrix} g_{x,0} &g_{x,0}\\ g_{x,1} &-\infty \end{bmatrix} \begin{bmatrix} f_{y,0}\\f_{y,1} \end{bmatrix} \]

这里的矩阵乘法是关于 \(\max\) 和加法运算的广义“矩阵乘法”。

考虑在重链上维护 \(g\) 值(线段树维护矩阵乘积即可),因为一条重链的底端一定是叶子节点,而叶子节点就储存了初始的值,我们要查询一个点的 \(f\) 值,只需要查询一下这个点到其所在重链底端的矩阵的乘积就可以了。

再考虑修改操作,修改一个点的权值时,该点到根的路径经过 \(O(\log n)\) 条重链,每条重链的顶端都是其父节点的轻儿子,只有这些位置的 \(g\) 值会发生改变,再线段树上单点修改即可,复杂度 \(O(\log^2n)\)

这样这道题就做完了,时间复杂度 \(O(n\log^2 n)\)(不过还有一个 \(2^3\) 的常数)。

P5024 [NOIP2018 提高组] 保卫王国

如果会动态 dp 的话,这题就和模板一模一样了。

posted @ 2021-08-17 22:30  iMya_nlgau  阅读(141)  评论(1)    收藏  举报