DP notes

P4934 礼物

不知道什么类型 dp。

只枚举给出的数显然不行,考虑从值域入手。枚举值域内的所有数,枚举每一个数二进制下的子集,设 \(f_i\) 表示 \(i\) 这个数应该放在第几组:

\[f_i=\max\limits_{(j)_2\in (i)_2} f_j+vis_i \]

\(vis\) 表示是否是题给的数。

这样显然还是超时,子集枚举应该是 \(\operatorname{O}(3^k)\)。注意到只需要考虑从 \(i\) 中删去一个 \(1\)\(j\) 即可,其余在以前都计算过了:

\[f_i=\max\limits_{j=i\operatorname{xor}2^p,0\le p<k} f_j+vis_i \]

\(\operatorname{O}(2^k\times k+n)\)


P5664 [CSP-S2019] Emiya 家今天的饭

dp 带了容斥。

数学加 dp,这下更不会了,完蛋。

简单来说是给定一个矩阵,和每一个元素可以选的次数。同一行的元素只能选一次,同一列所选不能超过总的一半,不能不选,求总方案数。

每一行一个元素是非常友好的限制,而注意到不符合限制的列最多只有一列,我们可以默认每一行选一个,算总方案数和不符合列限制的方案数,相减即可。

总方案数十分好计算,\(s_i\) 为第 \(i\) 行的和,每一列算上不选一共有 \(s_i+1\) 种选择,去掉什么也不选的,则为:

\[\prod\limits_{i=1}^n(s_i+1)-1 \]

之后直接枚举每一列作为不符合限制那一列,记为 \(col\)

\(f_{i,j,k}\) 表示前 \(i\) 行在当前列选了 \(j\) 个,在其他列选了 \(k\) 个。则有:

\[f_{i,j,k}=f_{i-1,j,k}+a_{i,col}\times f_{i-1,j-1,k}+(s_i-a_{i,col})\times f_{i-1,j,k-1} \]

第一项是什么也不选,第二项是这一行选本列的,第三项是选其他列的,很好理解吧!

然后不符合条件的方案数:

\[\sum\limits_{j>k}f_{i,j,k} \]

但是这样是 \(\operatorname{O}(mn^3)\) 的,无法通过。经过查看题解深思熟虑以后,我们发现我们只关心 \(j>k\) 的情况,也就是 \(j-k>0\) ,不需要知道具体大小。那重新设计状态,让第二维表示 \(j-k\) 的大小,即 \(f_{i,j}\) 表示前 \(i\) 行,当前行选的比其他行多了 \(j\) 个,状态转移方程为:

\[f_{i,j}=f_{i-1,j}+a_{i,col}\times f_{i,j-1}+(s_i-a_{i,col})\times f_{i-1,j+1} \]

这样可能会有负数下标,把第二维都加上 \(n\) 即可。

代码在这里


P1453 城市环路

无向基环树最大权独立集

我真的讨厌基环树 dp,*发怒*

本质上是一个没有上司的舞会,然后放到了基环树上,考虑随便断开一条环上的边,然后由两种做法:

  1. 对于端点 \(u\)\(v\),分别作为新树的根进行 dp,然后只统计 \(f[rt][0]\),这样可以覆盖所有情况。
  2. 钦定一个点为根,然后进行两次 dp,第一次强制选根,那另外那个叶子就强制不选,具体操作是 \(f[v][1]\gets \operatorname{INF}\);第二次强制不选根,就不需要额外操作了。

dp 的部分很简单,就不说了。

P2607 骑士

这个题就不单独说了,是一个有向基环树森林,只能采取上题的方法 \(2\)


AcWing 359 创世纪

首先注意到:\(i\) 限制 \(A[i]\),然后每种元素只限制另外一种元素,显然是基环树。我们从 \(A[i]\)\(i\) 连边,也就是一条边 \(u\to v\) 表示 \(u\)\(v\) 限制,是外向树森林。问题转化成一个外向树森林,一个节点如果要选,那么它的子节点至少有一个未选择;否则不选。要求选择的点最多。

考虑树上的做法。设 \(f[u][0/1]\) 表示 \(u\) 选或不选子树中选择的最多点数。

首先如果 \(u\) 不选,子节点随便。

如果 \(u\) 选,子节点要有一个不选,用一个小容斥,把贡献最小的去掉就好了:

\[f[u][0]=\sum\max(f[v][1],f[v][0]) \]

\[sum=\min\{max(f[v][1],f[v][0])-f[v][0]\} \]

\[f[u][1]=f[u][0]-sum+1 \]

代码


CF1955H. The Most Reckless Defense

给敌人加血可以看成是减少防御塔的攻击力,那么一个塔对敌人能造成的最大伤害即为 \(500\pi r^2-3^r\),注意到 \(r=12\) 时已经小于 \(0\) 了,所以半径不会很大,又因为每个 \(r\) 只能选一次,所以有效的塔很少,考虑状压 \(dp\)

具体地,我们设 \(f_{i,S}\) 表示前 \(i\) 个塔中,被选到的塔的半径集合为 \(S\),所造成的伤害(攻击力)为多少。显然最后答案就等于最大的伤害。有转移方程:

\[f_{i,S}=\max(f_{i-1,S},f_{i-1,S\oplus r}+p_i\times calc(i,r)-3^r) \]

含义很简单,就是当前选或不选。\(calc(i,r)\) 表示第 \(i\) 座塔半径为 \(r\) 时能攻击到几次敌人。预处理 \(calc\) 的复杂度为 \(\mathcal{O}(k\cdot R^3)\)\(dp\) 的复杂度为 \(\mathcal{O}(k\cdot R\cdot 2^R)\),总复杂度 \(\mathcal{O}(k\cdot R\cdot (R^2+2^R))\)

其实也可以最后一起减去 \(\sum 3^r\) 的,两种都对。一开始我不理解为啥两种写法是等价的,想了想,大概是因为每次取 \(\max\) 的两项只差一个 \(r\),所以要么都减了,要么都没减,不影响相对大小,也就不影响结果。

其实这题还能费用流。每个塔作为左部点,\(12\) 个半径作为右部点,边权或者说费用就是 \(p_i\times calc(i,r)-3^r\),跑一遍二分图带权最大匹配,也就是最大费用最大流即可。不过我没写,口胡的。

posted @   LHLeisus  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示