计数dp
错位排列计数 (组合意义dp)
题目
给定长度为 \(n\) 的排列,求解其错位排列数
题解
设 \(D_n\) 表示长度为 \(n\) 的排列的错排数,考虑我们已经知道了前 \(n-1\) 个错排数,那么对新加入的这第 \(n\) 个数进行分类讨论
- 直接与前面的数交换,有 \((n-1)\times D_{n-2}\) 种方案
- 放在前面某个数的位置上,那么相当于给原来 \(n-1\) 个数做了错排,有 \((n-1) \times D_{n-1}\) 中方案
所以
这个递推式可以求出其通项,但这不是本博客讨论的范围,读者感兴趣的自行了解即可
NOIP 四校联测 Day1 T2 (组合意义dp)
题目
给定两个排列 \(A,B\),求字典序介于 \(A,B\) 之间的排列最大前缀个数恰好为 \(k\) 的排列的个数
\(|A|,|B|\le 3000\)
题解
首先我们考虑没有字典序的限制要怎么做,设 \(f[i][j]\) 表示倒着往前考虑到第 \(i\) 个数,最大前缀个数为 \(j\) 时的排列的个数,转移时考虑组合意义
也就是我们当前的 \(i\) 是最小的那个数,我们考虑它如果放在最前面,那么前缀最大值的个数会 \(+1\),否则不会发生改变
考虑正解
一眼丁真,类似数位dp,应该是要进行一个差分的,也就是 \(\ge a\text{数的个数}-\ge b\text{数的个数}\)
于是我们考虑从前到后枚举每一位,以 \(\ge a\text{数的个数}\) 为例,假设 \(i\) 位之前都是相同的,我们枚举每一个在 \(A\) 数组的前 \(i-1\) 个位置没有出现过,而且比 \(a[i]\) 大的数字 \(B\) 填到位置 \(i\) 上
因此前 \(i\) 个位置我们已经填好了,且消除了对后面字典序的限制
考虑 \(1\sim n\) 中没有在前 \(i\) 个位置出现过的数字的集合为 \(S\) ,设我们已经填了的数字的最大值为 \(\text{mx}\) ,则 \(S\) 集合中所有小于 \(\text{mx}\) 的数字,都不会对前缀最大值的个数产生贡献
设前 \(i\) 个位置中,前缀最大值的个数为 \(C\) ,\(S\) 集合中大于 \(\text{mx}\) 的数字个数为 \(D\),那么有我们在 \(i+1\sim n\) 必须整出 \(k-C\) 个前缀最大值才可以
而 \(S\) 集合中小于 \(\text{mx}\) 的数是可以随便填的,所以当前对答案的贡献就是
然后把所有 \(i\) 对应的答案加起来就行了,时间复杂度 \(\mathcal{O} (nk)\)
NOIP 四校联测 Day2 构造数组 (辨认无意义的状压dp+组合意义dp)
题目
你现在有一个长度为 \(n\) 的数组 \(a\)。一开始,所有 \(a_i\) 均为 \(0\)。给出一个同样长度为 \(n\) 的目标数组 \(b\)。求有多少种方案,使得通过若干次以下操作,可以让 \(a\) 数组变成 \(b\)。
- 选出两个不同的下标 \(1\leq i<j\leq n\),并将 \(a_i\) 和 \(a_j\) 同时增加 \(1\)。
两种方案被称之为不同的,当且仅当存在一个 \(x\) 使得一种方案中第 \(x\) 次操作选择的两个下标 \((i,j)\) 与另一种方案中的不同。
\(1\le n\le5~000\),\(1\leq b_i\le30~000\),\(\sum b_i\le30~000\)
题解
这道题感觉出的挺好的,部分分给的挺多的,正解是不太好想的,考场上花了好长时间想,最后也没调出来
首先我们可以将这个问题转化成,我现在有 \(\frac{n}{2}\) 个容量为 \(2\) 的桶,然后我要往这些桶里面放东西。
一种朴素的状压dp做法是,考虑设 \(f[i][s]\) 表示考虑到第 \(i\) 个元素,各个桶的放置情况是 \(s\) 时的方案数。
然而我们发现我们其实并不关心每个桶放数的情况,我们只关心放了 \(0\) 个,\(1\) 个,\(2\) 个桶的数的个数,因此我们可以将状态设为 \(f[i][j][k]\) 表示考虑到第 \(i\) 个元素,个数为 \(1\) 的桶有 \(j\) 个,个数为 \(2\) 的桶有 \(k\) 个时的方案数,这样可以推出个数为 \(0\) 的桶的个数,转移时枚举当前元素有多少分给了 \(0\) ,多少分给了 \(1\) ,组合数计算转移即可
更进一步,我们发现知道了个数为 \(2\) 的桶的数量,我们是可以计算出考虑到第 \(i\) 个元素时个数为 \(1\) 的桶的数量的(因为前面的桶一定要全选完,不然没法跟后面的桶配对),转移同上
时间复杂度 \(\mathcal O\left((\sum b_i)^2\right)\)
容斥原理套路于DP
容斥原理在dp中有如下的经典应用:
- \(\text{一个也没有}=\text{所有情况}-\text{至少一个}+\text{至少两个}-{至少三个}+\cdots\)
- \(\text{所有限制}=\text{没有限制}-\text{一个限制}+\text{两个限制}-\cdots\)
- \(\text{恰好有}k\text{个}=\text{至少有}k\text{个}-\binom{k+1}{k}\text{至少有}k+1\text{个}+\binom{k+2}{k}\text{至少有}k+2\text{个}-\cdots\)
- \(\text{min-max容斥}\) \(\max(s)=\sum_{t\subset s}(-1)^{|t|-1}\min(t)\)
一般容斥原理直接暴力做的复杂度是 \(\mathcal O(n2^n)\),需要我们用dp或者多项式进行优化
P5664 [CSP-S2019] Emiya 家今天的饭 (容斥原理套路1)
题解
首先,如果直接考虑本题合法的方案数,我们发现题目中的限制有些过于多了,正难则反,考虑容斥
所以我们可以钦定一定有某一种食材超过了限制,那么答案就是总的方案数减去不合法的方案数。设 \(f[i][j][k]\) 表示考虑到第 \(i\) 种烹饪方法,超限食材使用了 \(j\) 次,其它食材使用了 \(k\) 次的方案数; \(\text{sum}\) 表示第 \(i\) 种烹饪方式所能做的菜的种类的和。那么枚举当前考虑的食材是 \(c\) ,那么有转移
不合法的方案数就是
设 \(g[i][j]\) 表示考虑到第 \(i\) 种烹饪方式,使用了 \(j\) 种食材时的总方案数,转移为
于是
就是我们的总方案数
综合来看,我们目前的时间复杂度是 \(\mathcal O(n^3m)\) 的,无法通过本题,于是考虑优化
这就是这道题最经典的地方了,我们发现对于 \(j,k\) 这两维,我们最后统计答案的时候关心的仅仅只是它们的相对大小关系,因此我们更改状态为 \(f[i][j]\) 表示考虑到第 \(i\) 种烹饪方式,钦定的使用数量最多的那一种食材相对其它食材的总和的差为 \(j\),那么转移就变成了
于是时间复杂度就降为了 \(\mathcal O(n^2m)\)
P1450 [HAOI2008] 硬币购物 (容斥原理套路1)
题目
共有 \(4\) 种硬币。面值分别为 \(c_1,c_2,c_3,c_4\)。
某人去商店买东西,去了 \(n\) 次,对于每次购买,他带了 \(d_i\) 枚 \(i\) 种硬币,想购买 \(s\) 的价值的东西。请问每次有多少种付款方法。
\(1 \leq c_i, d_i, s \leq 10^5\),\(1 \leq n \leq 1000\)。
题解
与上一道题同理,直接利用分组背包计算合法的方案数肯定是T飞了的,所以同样考虑容斥
我们分别钦定有 \(1\) 种, \(2\) 种, \(3\) 种 ,\(4\) 种硬币超过了限定了数量,没有限定的硬币随便选,假设当前选择的超限硬币组成的集合为 \(S\) 那么我们每次就相当于做一个背包容量为 \(s-\sum_{i\in S}(d_i+1)\times c_i\) 的完全背包,容斥一下,奇加偶减即可,这个时间复杂度就降为了 \(\mathcal O(n)\) ,采用预处理完全背包的做法的话带一个 \(4\) 的常数,不预处理也是可以通过的
BZOJ3622 已经没什么好害怕的了 (容斥原理套路3)
题目
给定 \(a\),\(b\) 两个长度均为 \(n\) 的数列,我们现在要对它们进行两两配对,问配对后 \(a\) 比 \(b\) 大的个数比 \(b\) 比 \(a\) 大的个数多 \(k\) 组的方案数
\(n\le 5000\)
题解
无序的序列过于丑陋,所以我们先要对两个序列先进行一个排序
设 \(p[i]\) 表示使得 \(b[j]<a[i]\) 最大的 \(j\)
同样与上一题类似,设 \(f[i][j]\) 表示考虑到 \(a\) 序列的第 \(i\) 位,已有 \(j\) 组 \(a>b\) 的方案数,剩下的随意分配,即对于所有的 \(f[n][i]\) 都要乘上一个 \((n-i)!\)
我们想要的就是 \(a>b\) d的组数恰好为 \(\frac{n-k}{2}\) 的方案数
我们现在对 \(f[n][i]\) 中 \(i > \frac{n-k}{2}\) 的部分进行一个容斥,令 \(m=\frac{n-k}{2}\) ,那么就有
时间复杂度 \(\mathcal O(n^2)\)
HDU 4336 Card Collector (容斥原理套路4 min-max容斥)
题目
有 \(n\) 张卡牌,每次拆开一包方便面有 \(p_i\) 的概率得到第 \(i\) 张卡牌,保证 \(p_1 + p_2 + \cdots + p_n \le 1\) , 所以方便面里可能存在没有牌的情况,求收集完 \(n\) 张卡牌,需要拆开的方便面包数的期望
题解
\(\text{min-max容斥}\) 常用于解决形如“全部出现的期望时间”问题,处理方法如下:
记 \(t_i\) 为第 \(i\) 个物品出现的时间, \(\min(S)\) 表示集合 \(S\) 中的物品至少一个出现的期望时间,\(\max(S)\) 表示集合 \(S\) 中的物品全部出现的期望时间
又因为单位时间内 \(S\) 中出现至少一个的概率为 \(p\),那么在 \(S\) 中出现至少一个的期望时间就是 \(\frac{1}{p}\)
所以,对于本题,
我们套用 \(\text{min-max容斥}\) 的公式就可以过掉这道题了
时间复杂度 \(\mathcal O(2^n)\)
ARC101E Ribbons on Tree (容斥原理套路2)
题目
给定一个 \(n\) 个点的树,\(n\) 为偶数,点与点之间两两配对,配对点之间的边被覆盖,问有多少种配对方式,使得每一条边都有被覆盖
\(n\le 5000\)
题解
直接计算是 \(\mathcal O(n^3)\) 的,无法通过本题
所以考虑容斥,设 \(S\) 表示没有被覆盖的边的集合,将这些视作断掉的边,那么就有
其中 \(f(S)\) 表示不能覆盖到 \(S\) 中边的方案数,那么答案就是
设 \(g(n)\) 表示 \(n\) 个点任意配对的方案数,那么有 \(g(n)=n!!\) (双阶乘)
设 \(\text{dp}[u][x]\) 表示结点 \(u\) 所在联通块大小为 \(x\) 时的方案数,注意此时 \(u\) 所在的联通块并没有独立出来,没有算在联通块总数中
对于 \(x\ge 1\) 的情况,转移就是树上背包,复杂度证明与"BZOJ 4033 树上染色"是一样的
对于 \(\text{dp}[u][0]\) 的情况,此时我们就将当前点 \(u\) 所在的联通块独立出来,也就是钦定了 \(u\) 到其父亲的这条边被删掉了,我们有 \(\text{dp}[u][0]=-\sum_{x=1}^{\text{siz}_u}\text{dp}[u][x]\times g[x]\) 因为此时被删掉的边集大小大了 \(1\)
时间复杂度 \(\mathcal O(n^2)\)
BZOJ 4767 两双手 (容斥原理套路2)
题目
棋盘上 \((0,0)\) 处有一个棋子。棋子只有两种走法,分别对应向量 \((A_x,A_y),(B_x,B_y)\)。同时棋盘上有 \(n\) 个障碍点 \((x_i,y_i)\),棋子在任何时刻都不能跳到障碍点。
求棋子从 \((0,0)\) 跳到 \((E_x,E_y)\) 的方案数。答案对 \(10^9+7\) 取模
\(E_x,E_y,n\le 5000\),保证 \((A_x,A_y)\) 与 \((B_x,B_y)\) 不共线
题解
因为两个向量不共线,由平面向量基本定理,加之旗子只有两种走法,我们可以找到对于一个变换后坐标为 \((x_1,y_1)\) 的点 \(A\) 和坐标为 \((x_2,y_2)\) 的点 \(B\),那么从前者走到后者的方案数就是 \(\binom{x_2-x_1+y_2-y_1}{x_2-x_1}\),记这个方案数为 \(\text{cnt}[A][B]\)
接着我们考虑容斥原理的套路 \(2\),设 \(f[i]\) 表示 \(i\)障碍之前不经过任何障碍到达障碍 \(i\) 的方案数,转移有
时间复杂度是 \(\mathcal O(n^2)\)
ZR2022 NOIP十连测 Day5 T3 (数位dp+容斥原理套路1)
题目
小K想知道你可不可以帮他验算。
小K有 \(k\) 个在 \([0,x]\) 范围内的随机整数 \(a_1,\ldots,a_k\),记 \(f(x)\) 表示 \(x\) 的所有非零数位的积,例如 \(f(0)=1,f(1145141919810)=1\times1\times4\times5\times1\times4\times 1\times9\times1\times9\times8\times1f(0)=1,f(1145141919810)=1\times1\times4\times5\times1\times4\times 1\times9\times1\times9\times8\times1\)。现在小 \(k\) 想知道 \(f(\sum_{i=1}^ka_i)\) 的期望值。
小K已经用超级计算机计算出了精确值,所以你只需要算出答案乘上 \((x+1)^k\) 对 \(10^9+7\) 取模的值帮他验算就好了
\(k\le 20,n\le 10^{10^{4}}\)
题解
毒瘤题!
乍一看就很像一个数位dp,但这道题困难的地方在于如何求出每一个 \(f(n)\) 它的系数是多少,并且如何将这个系数分离出来变成数位dp可做的东西
然后难的就是这一部分,所以我把这题归到了计数dp中(尽管它看着更像是一个数位dp)
我们设 \(g(n)\) 表示使得 \(\sum_{i=1}^ka_i=n\) 的方案数,那么考虑容斥,有
组合意义就是对大于 \(x+1\) 的数的个数进行容斥,后面那一部分考虑整体加一后插板
于是我们要求的答案就变成了
其中
这个东西显然是可以 \(\mathcal O(k^4)\) 暴力做的
这样我们的问题就转化为求
我们参考以往数位dp的经验,对上面那个式子进行转移,设 \(\text{dp}[j]=\sum_{i=0}^{kx} f(i)i^j\) ,假设我们枚举当前位 \(\text{now}\) 的取值为 \(x\) ,加完上一位时得到的数位 \(\text{num}\) 那么有转移
即从
转移而来
最后注意 \(\binom{n}{m},(n< m)\) 时是 \(0\) ,而我们在算系数的时候会把这玩意算负,所以最后那个式子改成
时间复杂度 \(\mathcal O(k^4+k^3\lg x)\)
[SRM 697] ConnectedStates (prufer序列计数)
题目
有\(n\)个城市,每个城市有个权值\(w_i\),任意两个城市\(i,j\)之间的道路数有\(w_i\times w_j\)条。对于每种生成树,若每个点的度数为\(d_i\),则贡献为 \(\prod d_i\)。求所有无根生成树的权值和。
\(n\le 1000\)
题解
吐槽:真毒瘤
这是一个有标号无根树问题,所以我们可以考虑搞出它的\(\text{prufer}\)序列,每个点的个数就是\(\text{prufer}\)序列中出现的次数\(+1\)
对于一个固定的\(\text{prufer}\)序列(无根树),方案总数显然是\(\prod w_i^{d_i}\)
贡献就再乘上度数之积为\(\prod d_iw_i^{d_i}\)
然后我们再算有多少种\(\text{prufer}\)序列,每个数都有一个出现次数的全排列显然是多重组合数,所以方案数有
因此我们最终所要求的东西就是
设\(f[i][j]\)表示考虑到第\(i\)个数,度数和为\(j\)的方案数,枚举当前位的度数\(k\),转移就乘上一个\(\frac{kw_i^{k}}{(k-1)!}\),这部分可以预处理出来
所以暴力转移的复杂度是\(\mathcal O(n^3)\)的
考虑使用多项式的幂公式,即
我们令\(a_i=d_i-1\),那么有
现在我们离化简只差\(\prod (a_i+1)\)这个东西了,考虑它实际上是在枚举子集(枚举\(a_1\sim a_n\)几个相互组成的单项式),我们考虑当前我们枚举到的单项式是\(a_2a_3a_7\),那么这个单项式对答案的贡献为(注意单项式被乘进了分母里,所以对应的加和也要减\(3\)),前面\((n-2)!\prod w_i\)就先不管了
所以类比到整个式子就变成了
\(f[t][k]\) 表示只考虑使用前 \(t\) 个数组合,所有 \(k\) 项式之和是多少。
转移就是 \(f[t][k]=f[t−1][k]+f[t−1][k−1]\times w_t\)
那么这道题就可以 \(\mathcal O(n^2)\)
ZR2023 NOIP赛前20连测 Day1 T3 (组合意义转多项式优化dp)
[题意](【noip赛前20天冲刺集训 day1】迷路 - 题目 - Zhengrui Online Judge (zhengruioi.com))
题解
题目的意思可以简化成,从每个起点出发,根据是否碰到树可以记作 \(0/1\) ,这样每条路径就是一个 \(01\) 串,题目要求的就是这 \(r\) 个 \(01\) 串两两不同的期望步数,起点一共有 \(\binom{n\times m}{r}\) 种选法,考虑统计方案数
从第一步到第 \(n\times m\) 步依次考虑,我们将无法区分的起点集合放在一个等价类中,那么这个过程可以视作等价类的分裂,由于有树的地方有限(记所有 \(01\)$ 中有树的位置个数为 \(T\)),所以我们可以在每个有树的地方,将所有有树的 \(01\) 串,从其所在的等价类中分离出来,这个过程可以用 \(\text{hash}\) 动态维护,时间复杂度 \(\mathcal O(nmT)\)
考虑 dp ,设 \(f[i][j]\) 表示考虑到第 \(i\) 个等价类,选了 \(j\) 个集合,使得它们处于不同等价类中的方案数,这样计算的时间复杂度是 \(\mathcal O(n^2m^2r^2)\) ,非常不优
于是这里开始上多项式了,我们设当前等价类的大小分别为 \(a_1,a_2,\cdots ,a_k\) ,方案数即为 \([x^r]\prod_{i=1}^k(1+a_ix)\) ,每一次把一个大小为 \(a\) 的等价类分裂为 \(b\) 和 \(c\) (这里 \(a,b,c\) 的大小通过前面的 \(\text{hash}\) 求出)就是给这个多项式除一个 \(a+1\) 并乘上 \((b+1)\times (c+1)\) ,由于我们只需要知道这个多项式的前 \(r\) 项,所以这里单次转移的时间复杂度是 \(\mathcal O(r)\) 的,整个 dp 的时间复杂度就为 \(\mathcal O(nmr)\)
这样总时间复杂度即为 \(\mathcal O(nmk+nmr)\)
ZR2023 NOIP赛前20连测 Day6 T3 (插板法+底数较小组合数的处理方法)
题意
你当前想要做一个有关概率的问题。
具体的,有 \(n\) 个随机变量,每个随机变量以相同的概率分布在 \([0,9]\) 。
当前你需要计算有多少概率使得变量的和小于等于为 \(k\)。
当然你并不满足于此,你尝试将这 \(n\) 个变量顺序排成一个十进制数(第 \(1\) 个变量是最高位,第 \(n\) 个变量是个位)。
要求这个十进制数大小 \(\mod p =0\)。
你需要对 \(k=0\sim m\) 都求出来结果。
为了方便,你只需要输出满足条件的概率乘上 \(10^n\)。
由于答案可能很大,你只需要输出答案在 \(\mod 998244353\) 意义下的结果。
对于所有测试点满足 \(1\leq n\leq 10^9,1\leq p\leq 50,1\le m \le 1000\)
题解
首先,你仔细想一下我们上面提到的常规数位dp做法,会发现它很困难,出题人的意思是可以用多项式做,挺抽象的
所以我们考虑另一种做法,我们将所有的 \(k\) ,按照 \(10^k\bmod p\) 的值分成若干剩余类,然后考虑 dp,设 \(f_{i,j,k}\) 表示考虑到第 \(i\) 个剩余类,当前数位和为 \(j\) ,当前数模 \(p\) 的结果是 \(k\) 时的方案数。我们记 \(g_{i,j}\) 表示第 \(i\) 个剩余类对数位和产生 \(j\) 的贡献的方案数。设当前我们要给数位和加 \(d\) ,那么转移方程即为
考虑如何求出 \(g_{i,j}\) ,首先通过找 \(10^k\) 模 \(p\) 的循环节,我们很容易算出来每个剩余类的大小为多少,记作 \(\text{cnt}_i\) ,考虑我们现在的贡献 \(j\) 都是由剩余类 \(i\) 的哪些位置组成的,这里可以插板法算,也就是把 \(\text{cnt}_i\) 个板插到 \(j\) 个位置上,板 \(k\) 和板 \(k+1\) 之间的数的个数就是第 \(k\) 个位置所产生的贡献大小,这里的方案数即为。
注意到一个位置的贡献最高是 \(9\) ,所以我们考虑容斥,钦定一些位置的贡献至少是 \(10\) ,钦定了 \(k\) 个位置,系数就是 \((-1)^k\binom{\text{cnt}_i}{k}\) ,然后还要把 \(j\) 减去 \(10k\)
然后由于 \(\text{cnt}_i\) 和 \(n\) 同阶,所以我们算上面的组合数需要用到下降幂,即
总时间复杂度即为 \(\mathcal O(m^2p^2)\)
带注释代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int read()
{
int x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-1;
for(;isdigit(ch);ch=getchar()) x=(x<<1)+(x<<3)+(ch^48);
return x*f;
}
const int mod=998244353,M=1010,P=51;
int up[M],dw[M],fac[M],ifac[M];
int n,p,m,N;
inline int ksm(int a,int b)
{
int val=1;
while(b)
{
if(b&1) val=val*a%mod;
a=a*a%mod;
b>>=1;
}
return val;
}
inline void init(int n)
{
N=n;
dw[0]=1;
for(int i=1;i<M;++i) dw[i]=dw[i-1]*(n-i+1)%mod;
up[0]=1;
for(int i=1;i<M;++i) up[i]=up[i-1]*(n+i-1)%mod;
}
inline int Cd(int x)
{
if(x>N) return 0;
return dw[x]*ifac[x]%mod;
}
inline int Cu(int x)
{
return up[x]*ifac[x]%mod;
}
int ok[M],id[M],val[M],cnt[M];
int g[P][M];
int f[P][M][P];
signed main()
{
n=read();p=read();m=read();
fac[0]=1;
for(int i=1;i<M;++i) fac[i]=fac[i-1]*i%mod;
ifac[M-1]=ksm(fac[M-1],mod-2);
for(int i=M-2;i>=0;--i) ifac[i]=ifac[i+1]*(i+1)%mod;
int u=1,tot=0,L=0,R=-1;
for(int i=1;i<=n;++i)//找模p意义下的循环节,用来计算各个剩余类的大小
{
u%=p;
if(ok[u])
{
L=id[u];R=i-1;
break;
}
++cnt[u];
id[u]=++tot;
val[tot]=u;
ok[u]=1;
u*=10;u%=p;
}
if(R!=-1)
{
int k=(n-R)/(R-L+1);
for(int i=L;i<=R;++i) cnt[val[i]]+=k;
u=val[R]*10%p;
for(int i=R+k*(R-L+1)+1;i<=n;++i)
{
u%=p;
++cnt[u];
id[u]=++tot;
val[tot]=u;
ok[u]=1;
u*=10;u%=p;
}
}
for(int i=0;i<p;++i)
{
init(cnt[i]);
for(int j=0;j<=m;++j)
{
int res=0;
for(int k=0;;++k)
{
if(j<10*k) break;
if(k&1) res+=(mod-1)*Cd(k)%mod*Cu(j-10*k)%mod;
else res+=Cd(k)*Cu(j-10*k)%mod;
}
g[i][j]=res%mod;//第 i 个剩余类,给数位和贡献 j 的方案数
}
}
f[0][0][0]=1;//考虑到第 i 个剩余类 ,数位和为 j ,取模结果为 k 时的方案数
for(int i=0;i<p;++i)
for(int j=0;j<=m;++j)
for(int k=0;k<p;++k)
{
if(!f[i][j][k]) continue;
for(int d=0;d<=min(m-j,cnt[i]*9);++d)// 当前考虑贡献 d 到数位和
(f[i+1][j+d][(k+d*i)%p]+=f[i][j][k]*g[i][d])%=mod;
}
for(int i=1;i<=m;++i) (f[p][i][0]+=f[p][i-1][0])%=mod;
for(int i=0;i<=m;++i) cout<<f[p][i][0]<<' ';
return 0;
}
ZR2023 NOIP赛前20连测 Day7 T3 (强限制优先+调和级数)
题意
有 \(n\) 个人, 要分成若干组. 对于第 \(i\) 个人, 它所在的组的人数不能超过 \(a_i\), 求分组的方案数.
组与组之间是不可区分的, 每组的人也是无序的, 但人与人之间是可以区分的.
\(n\le 3000,a\le n\)
题解
由于大的组限制更强,所以我们贪心地考虑,排序之后根据组的大小从大到小考虑
设 \(f_{i,j}\) 表示考虑到第 \(i\) 个组,有 \(j\) 个人还没有被分组的方案数。假设我们现在已经处理完了 \(i+1\sim n\) ,考虑如何转移。没有被分组的加上当前组的大小 \(\text{cnt}_i\) 就是我们现在的人数。然后考虑给这些人分组,我们设 \(k\) 表示 \(i+1\) 的组的个数,那么每次的转移系数就为
第二个组合数下面有 \(k\) 个 \(i\)
由于 \(k\times i\le j+\text{cnt}_i\) ,因此 \(k\) 的个数是 \(\log n\) 级别的
时间复杂度 \(\mathcal O(n^2\log n)\)
ZR2023 NOIP赛前20连测 Day9 T3 (组合意义转多项式优化dp)
题意
假设存在 \(k\) 种不同的括号 \((_1\cdots(_k,)_1\cdots)_k\),定义:
若序列 \(S_1,S_2\) 为第一类括号序列,则 \(\forall_i,(_iS_1)_i 与 S_1S_2\) 均为第一类括号序列。
若序列 \(S_1,S_2\) 为第二类括号序列,则 \(\forall_i,(_iS_1)_i,)_iS_1(_i\) 与 \(S_1S_2\) 均为第二类括号序列。
空串既是第一类括号序列又是第二类括号序列。
简单来说,第一类括号序列只允许匹配时左括号在左边,右括号在右边,而第二类括号序列允许相反顺序的括号匹配。你的任务是,给定正整数 \(n,k\),求出有 \(k\) 个不同括号之时,长度恰好为 \(2n\) 的第一类括号序列个数和第二类括号序列的个数。
由于答案可能很大,对 998244353 取模即可。
对于所有数据,\(1 \le n \le 10^6,1 \le k \le 10^9\)
题解
\(T=1\) 的情况是经典的卡特兰数,乘上括号选取的方案数 \(k^n\) 即可
\(k=1,T=2\) 的情况考虑组合意义,我们就是要在 \(2n\) 的序列上选 \(n\) 个位置为左括号,剩下的为右括号,也就是 \(\binom{2n}{n}\)
\(T=2\) 时,考虑直接乘选取方案数 \(k^n\) 为啥不对。比如括号序列 ()() ,它既可以看成 \((1,4),(2,3)\) 匹配,也可以看成 \((1,2),(3,4)\) 匹配,也就是它可以从中间切分,这样就会算重,所以考虑 dp
设 \(f_n\) 表示不可切分的括号序列个数,\(g_n\) 表示括号序列的个数,那么有转移方程
第一个式子考虑容斥,总方案数 \(g_{n-1}\) 减掉不合法的方案即为合法方案(无切分)。枚举切分点 \(i\) ,钦定前 \(i\) 个一定合法(不可切分),后 \(n-i\) 个随便填,注意两端括号选取的总方案数为 \(2k\) ,这样就可以算出不合法方案数
第二个式子同样考虑枚举切分点
接下来考虑使用 GF 技巧来快速计算上面的式子
我们设 \(F\) 和 \(G\) 分别对应 \(f\) 数组和 \(g\) 数组的多项式,那么我们可以列出下面两个式子
第一个式子乘 \(x\) 是因为要整体右移一位,减 1 减去的是常数项
第二个式子考虑一个括号序列可以由多个不可切分的括号序列组成,所以式子形如
然后这个东西的封闭形式是经典的 \(\dfrac{1}{1-x}\) ,把 \(F-1\) 套进去就行
然后通过我们高超的初二数学技巧,我们解出来
\(1-k\) 和 \(k\) 就是一个只有常数项的单项式,\(\dfrac{1}{1-4k^2x}\) 就是数列 \(p_n =(4k^2)^n\) 所对应的多项式的封闭形式,这样我们只要把封闭形式为 \(\sqrt {1-(8k-4)x}\) 的多项式的系数给求出来即可
然后这里题解给了一个很微妙的类似于求解常微分方程的做法,具体来说,设
对 \(H\) 求导得
分母有理化得
然后考虑第 \(n\) 项的系数, \(H'\) 就相当于是将 \(H\) 整体左移一位再乘上一个指数,也就是 \((n+1)h_{n+1}\),右边分母的 \(x\) 乘到左边就相当于整体右移一位,即 \(nh_{n}\) ,所以根据上面那个式子可以建立 \(h_n\) 与 \(h_{n+1}\) 之间的方程:
得到
这样这道题就做完了,由于只用求 \([x^n]G(x)\) ,所以时间复杂度 \(\mathcal O(n)\)
代码里懒得线性求逆元了,所以复杂度是 \(\mathcal O(n\log n)\) 的
posted on 2023-10-27 18:07 star_road_xyz 阅读(28) 评论(0) 编辑 收藏 举报