计数类 \(DP\) 在组合计数中是一类十分常用的算法,下面笔者以一些例题作为讲解,一来自己复习,二来可以帮助提升对计数题的思维与感受,就计数问题而言,多做这类题目才会找到感觉,其中会插入一些二项式反演的内容,也是和计数息息相关的。
这些题目,关键在于理解,而不在于实现的代码,所以笔者建议在做这一类题目时,不用去实现代码,理解思想即可,既能提升思维,又节约时间,这里总结了一些比较好的计数题目。
做计数类问题,一般要考虑上一状态与当前要计算的状态之间的对应关系,当前的状态必然是从上面的某状态形成的局面加上一部分比较可计算的东西得来的。
\(DP\) 转移方程一定要深刻理解,也要先尝试自己推,不过可以先看前面的一些例题,再去自己思考其他题目。
求组合数的几种方法:
\(1.\) 阶乘逆元法, \(O(n)\) 预处理, \(O(1)\) 查询 \(C_n^m\) ,因为预处理逆元要用到快速幂加上费马小定理,使用条件即费马小定理的使用条件,\(gcd(a,p)=1\) 且 \(p\) 是素数,这是最常用的方法,一般都不会给出违反费马小定理的数据。
\(2.\) \(Lucas\) 定理法,\(C_n^m\) \(mod\) \(p\) = \(C_{n/p}^{m/p}*C_{n\%p}^{m\%p}\%p\)
\(3.\) 定义法,不讲了。
CQOI2011 放棋子
题意
在一个 \(n\) 行 \(m\) 列的棋盘里放 \(c\) 种颜色的棋子,使得每个格子最多放一个棋子,且不同颜色的棋子不能在同一行或者同一列,有多少种方法?
如图,左图为合法,右图为不合法。
Solution
首先这样考虑,由于相同颜色的棋子会直接占领某些行和某些列,导致这些行和列组成的那些位置不能再放入其他颜色的棋子,所以每种棋子摆放的方案是相对独立的,会独立地占领一块区域,而这些区域的划分依据则是完整的行与完整的列。
那么我们就设 \(f[i][j][k]\) 表示前 \(k\) 种颜色填入任意 \(i\) 行 \(j\) 列之中(这类题都可以这么设),也即它们占领了 \(i\) 行 \(j\) 列,但这些被占领的完整行列可以是任意选取的。
考虑怎么转移,自然的想法是枚举当前的第 \(k\) 种颜色占领多少行多少列,那肯定就需要考虑当前,于是又设 \(g[i][j][k]\) 表示刚好某种颜色的 \(k\) 枚棋子放入任意的 \(i\) 行 \(j\) 列。
那 \(f[i][j][k]=\sum_{l=0}^{i-1} \sum_{r=0}^{j-1} f[l][r][k-1]*g[i-l][j-r][num[k]]*C_{n-l}^{i-l}*C_{m-r}^{j-r}\)
其中 \((i-l)(j-r) \ge num[k]\) ,原因是留出的空位一定要比那种颜色的棋子总数多,这样才放得下。
这里其实枚举的是前 \(k-1\) 种颜色放了多少行多少列,和上面提到的枚举当前是等价的,方案是对应的,那么当前就剩下 \(i-l\) 行和 \(j-r\) 列了,又因为任意选,所以再乘上两个组合数即可。
我认为比较容易出错的是 \(l\) 要从 \([0,i-1]\) , \(r\) 要从 \([0,j-1]\) ,想一下,第 \(k\) 种颜色必然占据至少一行和一列,而放第一种颜色时,棋盘上没有棋子,所以从 \(0\) 开始。
答案就是 \(\sum_{i=1}^n \sum_{j=1}^m f[i][j][c]\) 。
笔者认为这题比较难的是想出这个状态,方程是比较好写的。
[SCOI2005] 扫雷
题意
在 \(n*2\) 的棋盘里面,给定一列扫雷的数字,求有多少种雷的分布方式?
Solution
设 \(f[i][1/0][1/0]\) 表示考虑前 \(i\) 位的填数,当前这一位是否放,下一位是否放的方案数。
那么每一位只有 \(1.2.3\) 三种可能的数字,分类讨论即可。
如果填的 \(1\) 个数超过 \(a_i\) ,那这个状态的 \(DP\) 数组赋值 \(0\) 即可。
下面以 \(a_i=2\) 为例:
\(f[i][1][1]=f[i-1][0][1]\)
\(f[i][1][0]=f[i-1][1][1]\)
\(f[i][0][1]=f[i-1][0][1]\)
\(f[i][0][0]=0\)
打下草稿就会发现, \(i\) 阶段的方案数唯一对应着一种上一阶段 \(i-1\) 的方案数,正确性显然,
这个题学到的一个 \(trick\) 就是,设计状态时,如果只设计出包含当前和之前某状态的话,不好进行转移,那么不妨把接下来的状态加入方程。
例如本题,因为当前格子的数由三个位置决定,故需要考虑下一位,同时, \(i-1\) 某些状态也直接对应一部分 \(i\) 状态,加上即可。
[AHOI2009] 中国象棋
题意
在一个 \(n\) 行 \(m\) 列的棋盘上,让你放若干个炮(可以是 \(0\) 个),使得没有一个炮可以攻击到另一个炮,请问有多少种放置方法?
也就是说,每行每列最多两个炮。
Solution
这题感觉和第一个例题挺像的。
考虑到最多两个炮,于是在状态里把个数表示出来,设 \(f[i][j][k]\) 表示前 \(i\) 行,有 \(j\) 列一个炮,有 \(k\) 列两个炮。
则有 \(m-j-k\) 列没放,分别考虑当前行放置 \(0,1,2\) 个炮的情况,分类讨论。
这样的话,行的限制在转移时枚举得以满足,列的限制在状态中满足。
转移方程如下:
当前行放 \(0\) 个棋子:
\(f[i][j][k]=f[i-1][j][k]\)
当前行放 \(1\) 个棋子:
\(f[i][j][k]=f[i-1][j-1][k]*(m-j-k+1)+f[i-1][j+1][k-1]*(j+1)\)
理解一下,其实就是考虑了这个炮放在之前没有炮的地方还是有一个炮的地方。
当前行有 \(2\) 个炮,方法也是类似,答案就是 \(\sum_{l=0}^m\sum_{r=0}^mf[n][l][r]\) ,其中 \(l+r \le m\)。
当前状态的炮的分布,与上一状态的炮的分布十分相关,这题感觉也是状态较为难设,方程比较好写。
[CF559C] Gerald and Giant Chess
题意
给定一个 \(H*W\) 的棋盘,棋盘上只有 \(N\) 个格子是黑色的,其他格子都是白色的。在棋盘左上角有一个卒,每一步可以向右或者向下移动一格,并且不能移动到黑色格子中。求这个卒从左上角移动到右下角,一共有多少种可能的路线。
Solution
先总结一个常用结论,如果不考虑不能走的限制,那么从起点走到点 \((x,y)\) 的路线条数为 \(C_{x-1+y-1}^{y-1}\),为方便表达,我们记作 \(G(x,y)\) , 证明不在此赘述。
接下来显然是考虑容斥,用总的路线条数减去通过了黑色格子的不合法路线条数。设 \(f[i]\) 表示从起点走到第 \(i\) 个黑色格子,且中途不经过其他黑色格子的方案数。
那显然, \(f[i]=G(x_i,y_i)-\sum_{j=1}^nf[j]*G(x[i]-x[j]+1,y[i]-y[j]+1)\) ,其中 \(x[j]\le x[i]\) ,\(y[j]\le y[i]\)
意思就是,先走到之前某一个黑色格子,再任意走路线,保证这个黑色格子是第一次经过的黑色格子,这样就不重不漏地划分了状态。
利用已经求好的一些数据进行递推,这也是 \(DP\) 的一大妙处吧。
[POJ1737] Connected Graph
题意
求 \(𝑁\) 个节点的无向连通图有多少个,节点有编号,编号为 \([1,n]\) 。
Solution
图计数的经典问题,虽然现在似乎不怎么考图计数的问题,但思路是值得学习的,这题也有很多地方值得深究下去,下面会提及一些。
就还是那个套路,直接计数不好做,考虑容斥,数出不合法方案数。
设 \(f[i]\) 表示 \(i\) 个点形成的无向连通图个数,显然总数是 \(2^{\frac{n(n-1)}{2}}\) 。
转移只需枚举 \(1\) 号点所在的连通块大小,剩下的边可以任意连接,只是不与枚举的连通块连边。
想一下,为什么要加上 \(1\) 号点的限制而不是直接说成,固定一个大小为 \(i\) 的连通块?
因为这样存在算重的可能,如果按照这种方式DP的话,会出现下面这些情况。
假定先固定一个大小为 \(2\) 的连通块,剩下包含 \(1,2,3\) 的连通块。
再枚举到固定大小为 \(3\) 时,剩下包含 \(4,5\) 的连通块。
很明显,这两种情况是同一个图,如果只定义出连通块大小,不能避免这种算重的情况。
所以要想办法做到不重,这时候我们就以 \(1\) 号点作为划分标准,其实任取一个点作为划分标准都可以,同时也可以发现,之前那个出错的定义是比较模糊的,而将一个点加入限制后,就可以很好地解决重复的问题了。
所以就可以写出转移方程了,方程网上到处都是,我觉得也不必再写了,但是为何要设成这样的状态,在我看到的范围内,是没有题解讲到的,更需要分析出这一点。
以上题为止,是 \(NOIP2021\) 之前记录的题目,然而今年的 \(T2\) 就是一道计数 \(DP\) ,还是很有帮助的。