8.5~8.16 做题笔记

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


CF1548C

题意

q 个询问,每次给定 x,求 i=1N(3ix)1N106

做法一

fx,k=i=0N1(3i+kx),则有

  • fx,0+fx,1+fx,2=(3Nx+1)
  • fx,1=fx,0+fx1,0
  • fx,2=fx,1+fx1,1

递推就能做了。

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

0kn(km)=(n+1m+1)

做法二

 fx=i=1N(3ix)=i=1N((3i3x)+3(3i3x1)+3(3i3x2)+(3i3x3))=fx(3Nx)+3fx13(3Nx1)+3fx23(3Nx2)+fx3(3Nx3)=fx+3fx1+3fx2+fx3(3N+3x)

x3 替换成 x 就得到递推式

fx=(3N+3x+3)3fx+13fx+2

比方法一的简洁些。


斐波那契数列通项公式

Fn=15(αnβn)

其中 α=1+52,β=152

可以扩域做,即把 a+b5,(a,bQ) 看作广义的“复数”进行运算。


Codeforces Round #737 (Div. 2)

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


长链剖分

树上 k 级祖先

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

预处理:

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

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

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

  4. i[1,n] 求出在二进制下的最高位 hi

对于每次询问 xk 级祖先:

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

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

HOT-Hotels

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

fx,j 表示 x 的子树中到 x 距离为 j 的节点数,转移比较简单:

fx,j=yson(x)fy,j1

再设状态 gx,j 表示在 x 的子树中选两个点,它们到其 lca 的距离为 d 且 lca 到 u 的距离为 dj 的方案数。

转移:

gx,j=yson(x)(gx,j+1+fx,j×fy,j1)

这里的 fx 表示 fxy 之前的儿子更新过的值。

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

对答案的贡献为

yson(x)j(gx,j×fy,j1+fx,j×gy,j+1)

第二种情况,三个点有两个在子树内,另一个为祖先节点,对答案的贡献就是 gx,0

至此我们就有了 O(n2) 的解法。

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

CCPC-Final 2019 G

题意

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

1n2×105

做法

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

先设计这个恶心的 dp。

fx,j 表示以 x 为根且最大深度为 j 的子图方案数。

转移:

fx,j=(kjfx,k)×fy,j1+fx,j×k<jfy,k

其中 yson(x)f 表示处理到当前儿子之前 f 的值,这是一个滚动的过程,下文的 g 同理。

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

  • gj,0 表示以 1 为根且最大深度小于 j 的子图方案;

  • gj,1 表示以 1 为根且仅有一个儿子中的最大深度为 j 的子图方案;

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

转移:

gj,2=gj,1×fy,j1+gj,2×k<jfy,kgj,1=gj,0×fy,j1+gj,1×k<j1fy,kgj,0=gj,0×k<j1fy,k

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

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

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

提交记录


luogu P1273

O(n2) 的树上背包

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

fi,j 为前 i 个点,背包容量为 j 的最大价值。设 sizi 为以 i 为跟的子树大小。那么 fi,j 就会从 fi1,j1fisizi,j 转移过来。

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


换根 dp

CF708C Centroids

题意

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

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

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

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

转移大概就是

fx={max{sizy}sizyn/2max{fy}otherwise

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

提交记录

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

P6419 [COCI2014-2015#1] Kamp

P3647 [APIO2014] 连珠线

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


P4292 [WC2010]重建计划

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

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

fx,j=maxyson(x){fy,j1+w(x,y)}

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

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


矩阵加速 dp

P2579 [ZJOI2005] 沼泽鳄鱼

注意到 2,3,4 的最小公倍数为 12,计算 12 个转移矩阵,乘起来之后求一个 k12 次幂,在乘上前 kmod12 个矩阵就是总的转移矩阵

P3216 [HNOI2011] 数学作业

状态转移方程:

fi=fi1+10k+i

其中 k=lgi+1

矩阵加速:

[fii1]=[10k11011001][fi1i11]

P2886 [USACO07NOV] Cow Relays G

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

P3502 [POI2010] CHO-Hamsters

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

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


P4095 [HEOI2013]Eden 的新背包问题

奇妙的 cdq 分治+多重背包

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

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

复杂度就是 O(nVlogn)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(nlog2n)

在题解下面看到一个做法

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

然后我们把所有在第一个森林里和 1 联通的点压入栈 s1,在第二个森林里和 1 联通的点压入栈 s2

然后不断进行一下操作:

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

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

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

CF1559E. Mocha and Stars

题意

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

  • ai[li,ri]Z
  • i=1naim
  • gcd(a1,a2,,an)=1

2n501m1051lirim

解题思路

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

莫反之后就是求

d=1md|gcd(a1,a2,,an)μ(d)

考虑每个 μ(d) 的贡献,把 ai 都除以 d。那就是,对于每个 d,求有多少个序列满足 ai[li/d,ri/d]Zaim/d

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

P4027 [NOI2007] 货币兑换

fi 为第 i 天可以得到的最多钱数,然后列个方程解出第 j 天花钱买到的两种金券数 xi=fjrjajrj+bj,yi=fjajrj+bj

状态转移方程就来了:fi=max{aixj+biyj}

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

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

变个形:fi=bi×max{xj×aibi+yj}

xj,yj 看成斜率和截距,李超线段树即可,记得离散化。

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

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

P4655 [CEOI2017]Building Bridges

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


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

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

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

然后

fx,0=yson(x)max(fy,0,fy,1)fx,1=ai+yson(x)fy,0

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

动态 dp

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

fx,0=gx,0+max(fy,0,fy,1)fx,1=gx,1+fy,0

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

[fx,0fx,1]=[gx,0gx,0gx,1][fy,0fy,1]

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

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

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

这样这道题就做完了,时间复杂度 O(nlog2n)(不过还有一个 23 的常数)。

P5024 [NOIP2018 提高组] 保卫王国

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

posted @   iMya_nlgau  阅读(114)  评论(1编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
点击右上角即可分享
微信分享提示