计数dp入门
计数dp入门
前情提要:
- 能开滚动数组就开滚动数组,不开包你 \(MLE\)
- 不要畏惧洛谷难度标签,很多时候难的是性质的发掘而非计数时的 \(dp\),见多识广就能熟练运用甚至跨阶切题。
- 波奇酱世界第一可爱!!!
AT_codefestival_2016_final_f Road of the King
有一个 \(n\) 个点的图,目前一条边都没有。
有一个人在 \(1\) 号点要进行 \(m\) 次移动,终点 不必 是 \(1\) 号点,假设第 \(i\) 次从 \(u\) 移动到 \(v\),那么在 \(u\) 与 \(v\) 之间连一条有向边。
问有多少种序列能满足:最终 \(n\) 个点组成的图是一个强连通图。答案对 \(10^9+7\) 取模。
\(1\le n,m\le 300\)
没有思路?来点提示:一个图是强连通,等价于使得所有的点都能到达 \(1\),且 \(1\) 能到所有的点。
充分性:所有点可以通过先到 \(1\) ,再从 \(1\) 到达其他点的方式,到达所有点,满足强连通图一个点出发可以到达所有其他点的条件。
必要性:当某个点不能到 \(1\),或 \(1\) 不能到某个点,这本身就违反了定义。
考虑利用这个特殊性质,以 \(1\) 能到哪些点,以及哪些点能到 \(1\) 来设计状态。
我做的时候设的状态十分暴力,设 \(f_{i,j,k}\) 表示走了 \(i\) 条边, 已经有 \(j\) 个点被连入,此时 \(k\) 个点还不可达到 \(i\),我们每次转移相当于从当前出点连一条边,考虑三种情况:
- 连入当前不可到达 \(1\) 的点中,有 \(k\) 个点可供选择,\(\large f_{i,j,k}\times k\to f_{i+1,j,k}\)。
- 连入当前可到达 \(1\) 的点中,有 \(i-k\) 个点可供选择,考虑到添加点走的是一条路径,所有当前不可达 \(1\) 的点都可以到达当前出点,所以当当前出点能到 \(1\) 时,其他 \(k\) 个点全部能到达 \(1\),\(k\) 清零,\(\large f_{i,j,k}\times (i-k) \to f_{i+1,j,0}\)
- 连入新的点中,有 \(n-i\) 个点可供选择,\(\large f_{i,j,k}\times (n-k)\to f_{i+1,j+1,k+1}\)
答案就是 \(f_{m,n,0}\)。
总结两个点:
- 状态一般较为暴力的设为能将加入的东西的组合意义考虑到,且对当前局面有大概的代表性的东西
- 有时设状态需要对问题形式进行转化(一般为经典结论),以转化为能让状态方便描述局面的形式。
其实这道题就是纯粹的暴力设状态,分讨转移,虽然是紫题但可以发现还是很套路的,思维难度不算高,,本次 \(S\) 组比赛 \(t4\) 也是类似套路。
AT_arc059_d [ARC059F] バイナリハック
有一个空串,可以进行下面三种操作:
- 在末尾加入一个 0。
- 在末尾加入一个 1。
- 删去末尾的数,如果串为空则忽略。
求 \(n\) 次操作后满足整个串等于某个给定的串 \(S\) 的方案数模 \(10^9+7\)。\(n,|S|≤5×10^3\)。
比较有入门意义的一道题,我们删除字符的时候,两个字符是等价的,而在加入到合法时,留下的字符会自动变成给出的字符串,这启示我们不去关注加入的字符具体是什么,而去关注它的贡献及影响。
设 \(f_{i,j}\) 表示前 \(i\) 次操作,共打入 \(j\) 个字符的方案数。
现在假设我们加入了一个不知道是什么的数,此时我们不管他是 \(0\) 还是 \(1\),所以贡献系数为 \(1\),\(f_{i,j}\to f_{i+1,j+1}\) 。他在被删除时,我们是并不知道这个数是啥的,而他可以变成 \(0/1\) 两种数且都合法,所以此处贡献系数为 \(2\) ,\(f_{i,j}\times 2\to f_{i+1,j-1}\) ,最后操作完还剩下 \(s.size()\) 个字符,这些字符自动变成 \(s\) 每个位置的字符,所以只有一种方案,对答案没有贡献。
其实这题使用的不关注加入的数是什么,而在其删除/最终变化时才考虑他的不同选择带来的贡献,是计数 \(dp\) 常见的技巧,算是贡献延后的一种前瞻。
Hakone
有一个 \(1∼n\) 的排列 \(P\)。现在对于每个 \(i\),给出 \(P_i\) 和 \(i\) 的大小关系(\(P_i>i/P_i = i/P_i<i\)),求可能的 \(P\) 的数量模 \(10^9+7\)。\(n≤200\)。
来点看起来很高级的做法(建议仔细关注其设的 \(dp\) 状态):
考虑建一张二分图,第 \(i\) 个左部点向所有可能的 \(P_i\) 的取值的右部点连边,则问题变成了求该二分图的最大匹配数。显然可以删去所有满足 \(P_i=i\) 的对应 \(i\),则之后的每个 \(P_i≠i\),方便之后的讨论。
显然无论单独看哪一部分的点,钦定每个点匹配的点时都会十分困难(无法确定其对之后的影响)。考虑每次同时处理第 \(i\) 个左部点/右部点,且对于一对匹配点,只在编号更大的点统计贡献(避免后效性)。设 \(dpi,j,k\) 为考虑了前 \(i\) 对点,且 \(j\) 个左部点和 \(k\) 个右部点未匹配的方案数(显然 \(j\) 个左部点只向编号更大的右部点连边)。如果 \(P_i+1>i+1\),则该左部点只能向编号更大的右部点连边,\(dp_{i+1,j+1,k+1}←dp_{i,j,k}\);否则其需要向编号更小的右部点连边,\(dp_{i+1,j,k}←k×dp_{i,j,k}\)。注意右部点可能和之前的 \(j\) 个左部点之一匹配,可能不匹配,所以还有 \(dp_{i+1,j,k}←j×dp_{i,j,k},dp_{i+1,j−1,k−1}←j×k×dp_{i,j,k}\)。注意到合法的 \(dp\) 状态一定满足 \(j=k\),所以可以合并后两维。
之所以让大家仔细看这个状态定义,是因为这个状态太脑瘫了(从别人题解看到的状态,明明左部点未匹配数严格等于右部点,不知道为什么要多设一维)。。感觉这纯粹是看着数据范围硬凑到的 \(n^3\) 状态,而且这题也没必要强行转成二分图,其实用 \(n^2\) 就可以表示所有局面,且有其代表的具体局面意义,因此这道题完全可以升级成 \(n\le 5\times 10^3\),也是我自己在解这道题时忽略 \(n\) 的规模,而从此类问题常见的状态定义及转移想出来的做法。
让我们忘记上述思路,设 \(f_{i,j}\) 表示前 \(i\) 个点,\(P\) 不超过 \(i\) 的有 \(j\) 个,则 \(P\) 超过 \(j\) 的就有 \(i-j\) 个,这样把状态简化为 \(\Theta(n^2)\),考虑从 \(i\to i+1\),我们此时加入 \(i\),当 \(i>P_i\),这个点往前放,前面还没放的位置有 \(i-j\) 个。可当我们考虑往后放时,就爆了,因为你往后放是会在后面转移时造成影响的,即我们转移到 \(i+1\) 时,仅用这个状态无法让我们得知 \(i+1\) 是否被占用,似乎只有状压才能表示这个状态?但状压就爆的没边了,这个唐人状态不纯瞎设吗?
这个时候,我们就要考虑一个叫做状态延后的东西了(顺带鞭尸,考场上有两位实力不俗的高二学长在做 \(t4\) 时,一个想到状态却误以为这玩意转移不了,一个想出完整做法没时间打),以我的理解就是当前元素满足:
- 加入时无法被当前状态具体表示。
- 对后续的转移有影响。
- 可以得知对当前状态的影响。
此时先不考虑他具体的对后续的影响,此时把对当前状态影响相同的许多这种元素等价,并表示进状态里,在后续它要构成影响时把它拉出来决策,确定它的影响并加入贡献,这样就能在不表示它具体状态的前提下算出他的所有贡献。
考虑这道题为什么能贡献延后,首先我们需要决策把点加入 \(>i\) 时:
- 当前状态只表示了前 \(i\) 个,无法表示此点对状态的影响。
- 如果将他放置在后面的某个点,转移到这个点时它就会影响你转移,比如说点 \(i\) 不能选被之前某个点占用的 \(i+1\) 之类的。
- 我们知道它往左放的影响以及往右放 对当前状态的影响。
所以,当 \(P_i<i\) 且第 \(i+1\) 个没被占用时,\(f_{i,j}\times (i-j)\to f_{i+1,j+1}\),当第 \(i+1\) 位被占用时,我们还要从后面的不知道具体位置的 \(i-j\) 个点中选一个填,\(f_{i,j}\times (i-j)\times (i-j)\to f_{i+1,j+2}\),当 \(P_i>i\) 且第 \(i+1\) 位没被占用时,我们只知道他去后面了,但先不确定他的位置,也不算它取不同位置的贡献,则\(f_{i,j}\times f_{i+1,j}\),而当 \(i+1\) 位被占用,我们依旧从后面不知道位置的点中选一个确定到 \(i+1\),\(f_{i,j}\times (i-j) \to f_{i+1,j+1}\),而 \(i=P_i\) 时显然不会有对方案的贡献,直接 \(continue\),这样就在 \(\Theta(n^2)\) 时间内以更想得明白的方式过了这道题。
顺带一提,这道题的贡献系数除了延后的那个全是 \(i-j\),即一个状态对所有后继状态的影响都一样,说不定还有开发空间。
qoj9128.Priority Queue 3
题目:优先队列3
输入文件: 标准输入
输出文件: 标准输出
时间限制: 2秒
内存限制: 1024 MB
给定一个长度为 \(N+M\) 的字符串 \(S\),其中包含 \(N\) 个 + 字符和 \(M\) 个 - 字符,以及一个由 \(M\) 个整数组成的集合 \(A=\{A_1,A_2,…,A_M\}\)。
初始化两个集合 $X=\varnothing $ 和 $Y=\varnothing $,并按照 \(i=1,2,…,N+M\) 的顺序执行以下操作:
- •当 \(S\) 的第 \(i\) 个字符是
+时,从 \(1\) 到 \(N\) 中选择一个未被包含在 \(X\) 或 \(Y\) 中的整数,并将其加入 \(X\)。 - •当 \(S\) 的第 \(i\) 个字符是
-时,将 X中最小的整数 \(m\) 从 \(X\) 中移除,并将其加入 \(Y\)。根据约束条件,可以保证在此操作前 X非空。
共有 \(N!\) 种方式来确定加入 \(X\) 的整数顺序。求其中满足所有操作执行后 \(Y=A\) 的方式数目,并将答案对 \(998244353\) 取模后输出。
输入格式:
输入通过标准输入给出以下格式:
N M
S
A₁ A₂ … A_M
- 所有输入均为整数。
- \(1\le M\le N\le300\)
- \(S\) 是一个长度为 \(N+M\) 的字符串,由 \(N\) 个
+和 \(M\) 个-组成。 - 对于 \(i=1,2,…,N+M\),前 i个字符中出现的
-的数量不超过前 \(i\) 个字符中出现的+的数量。 - \(1\le A_1<A_1<\dots<A_M\le N\)
输出格式:
在一行中输出答案。
示例:
标准输入:
4 2
++-++
1 3
标准输出:
4
标准输入:
6 4
++-++---++
2 3 4 6
标准输出:
48
标准输入:
20 10
++++-++++++--+--+-+++++--+-++
1 2 3 4 5 6 7 9 12 13
标准输出:
179396825
备注:
对于第一个示例,一种满足条件的操作序列如下:
- \(i=1\):将 \(3\) 加入 \(X\),此时 \(X=\{3\},Y=\varnothing\)。
- \(i=2\):将 \(4\) 加入 \(X\),此时 \(X=\{3,4\},Y=\varnothing\)。
- \(i=3\):将最小整数 \(3\) 从 \(X\) 移入 \(Y\),此时 \(X=\{4\},Y=\{3\}\)。
- \(i=4\):将 \(2\) 加入 \(X\) ,此时 \(X=\{2,4\},Y=\{3\}\)。
- \(i=5\):将 \(1\) 加入 \(X\),此时 \(X=\{1,2,4\},Y=\{3\}\)。
- \(i=6\):将最小整数 \(1\) 从 \(X\) 移入 \(Y\),此时 \(X=\{2,4\},Y=\{1,3\}\)。
对于第二个示例,字符串 \(S\) 的末尾不一定以 - 结尾。
这道题算是小有难度的典题。
首先我们有个套路,如何计算
其次,这次我们需要从一个数在什么条件下能被加入入手考虑,把这个条件的限制转化为状态,在一个点决策时就可以依靠这个转移。
这道题这个限制,便是对于所有不是 \(Y\) 集合的数,要是要入第一个堆,它必须满足比所有 \(Y\) 集合中还没被加到第二个堆的数大,所以我们设状态时应该考虑记录 \(Y\) 集合元素的最大值。
以及,我们需要定义一个可以代表此时大概的局面的状态,如上一题的 \(P_i\le j\) 的 \(i\) 的个数。
设 \(f_{i,j,k,l}\) 表示前 \(i\) 次操作,\(Y\) 集合中还没入第二个堆的元素最大值为 \(a_j\),此时有 \(k\) 个 \(Y\) 集合中的数在第一个堆,\(l\) 为 \(0/1\) 变量,表示最大值是否在第一个堆。我们定义 \(s1_i\) 表示前 \(i\) 个操作中 \(+\) 的数量,\(s2_i\) 表示 \(-\) 的数量。
对于 \(+\) ,当我们加入 非 \(Y\) 集合元素时,可选的数的数量就是当前合法的 \(-\) 之前加过的,理由在“首先”中有讲,共有 \(n-a_j-(m-j)-(s1_i-s2_i-k)\) 个,所以 \(f_{i,j,k,l}\times (n-a_j-(m-j)-(s1_i-s2_i-k))\to f_{i+1,j,k,l}\)。当我们加入 \(Y\) 集合非最大值元素时,因为状态只能表示它加到第一个堆后有多少个 \(Y\) 集合中元素,我们依旧采用贡献延后,加入一个不知道是什么的元素,\(f_{i,j,k,l}\to f_{i+1,j,k+1,l}\),当我们加入最大值到第一个堆时,\(f_{i,j,k,0}\to f_{i+1,j,k+1,1}\)。
对于 \(-\),我们只会考虑 \(Y\) 集合中元素,在第一个堆中的 \(k-1\) 个元素都有可能在我 \(pop\) 掉当前最大值时成为下一个最大值,所以它对后续转移有影响,且状态没有表示出下一个最大值是哪个,所以在将非最大值的 \(Y\) 集合元素推出时依旧延后他的贡献,\(f_{i,j,k,l}\to f_{i+1,j,k-1,l}\),此时状态需满足 \(k>1\) 或 \(l=0\),即保证推出的不为最大值,而当我们推出最大值时,第一个堆中必定只剩它一个,不然它会推出更小的元素,所以转移的状态只有 \(f_{i,j,1,1}\),而我们此时也要计算延后的贡献并转移到新的最大值的状态上,考虑枚举 \(p\) 表示 \(Y\) 集合中新的最大值的下标,则在之前 \(p \sim j\) 之间的元素全被删除,而且因为是从当前最小值往后删,所以删除是没有顺序带来的贡献的,这些数的贡献只体现在加入的顺序以及在哪个 \(+\) 被加入上,因此从之前的 \(+\) 中选 \(j-p-1\) 个且注意顺序,\(f_{i,j,1,1}\times {s2_i-(m-j+1)\choose j-p-1}\times (j-p-1)! \to f_{i+1,p,0,0}\)。
P14364 [CSP-S 2025] 员工招聘 / employ
来点正赛题目收收尾,这道题其实个人感觉甚至没上一道题难,相信学会了延后贡献的你一定能随手秒掉。
首先第一维状态为常见的选 \(i\) 个人,然后设计对当前转移有影响的状态,即有 \(j\) 个人被拒绝,接下来虽然没有对转移的 \(c_i\) 有限制的东西了,但因为我们应该按照天的顺序加入 \(c_i\),所以我们无法把 \(c_i\) 按顺序插入,对于一个新的位置,应考虑所有可以加入的 \(c\),这时就要加入一个不会让我们算重的状态。
所以我们设第三维 \(k\) 表示此时有多少个人的 \(c\le j\)。这样在我们加入时就能知道有多少个满足条件的 \(c\) 还没被加入,这个时候就能计算加入的数的贡献了,且从没加入的数中选来转移也不会算重。
具体转移式就分讨,发现状态不能表示的就延后,码量也还好,比上一题甚至少点思维难度,这里不再赘述。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/961693.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!