计数dp

错位排列计数 (组合意义dp)

题目

给定长度为 \(n\) 的排列,求解其错位排列数

题解

\(D_n\) 表示长度为 \(n\) 的排列的错排数,考虑我们已经知道了前 \(n-1\) 个错排数,那么对新加入的这第 \(n\) 个数进行分类讨论

  • 直接与前面的数交换,有 \((n-1)\times D_{n-2}\) 种方案
  • 放在前面某个数的位置上,那么相当于给原来 \(n-1\) 个数做了错排,有 \((n-1) \times D_{n-1}\) 中方案
    所以

\[D_n=(n-1)\times (D_{n-1}+D_{n-2}) \]

这个递推式可以求出其通项,但这不是本博客讨论的范围,读者感兴趣的自行了解即可

NOIP 四校联测 Day1 T2 (组合意义dp)

题目

给定两个排列 \(A,B\),求字典序介于 \(A,B\) 之间的排列最大前缀个数恰好为 \(k\) 的排列的个数

\(|A|,|B|\le 3000\)

题解

首先我们考虑没有字典序的限制要怎么做,设 \(f[i][j]\) 表示倒着往前考虑到第 \(i\) 个数,最大前缀个数为 \(j\) 时的排列的个数,转移时考虑组合意义

\[f[i][j]=f[i-1][j-1]+(i-1)\times f[i-1][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}\) 的数是可以随便填的,所以当前对答案的贡献就是

\[\binom{D}{k-C}\times f[D][k-C] \times (n-i-D)! \]

然后把所有 \(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中有如下的经典应用:

  1. \(\text{一个也没有}=\text{所有情况}-\text{至少一个}+\text{至少两个}-{至少三个}+\cdots\)
  2. \(\text{所有限制}=\text{没有限制}-\text{一个限制}+\text{两个限制}-\cdots\)
  3. \(\text{恰好有}k\text{个}=\text{至少有}k\text{个}-\binom{k+1}{k}\text{至少有}k+1\text{个}+\binom{k+2}{k}\text{至少有}k+2\text{个}-\cdots\)
  4. \(\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\) ,那么有转移

\[f[i][j][k]=f[i-1][j][k]+f[i-1][j-1][k]\times a[i][c]+f[i-1][j][k-1]\times (\text{sum}[i]-a[i][c]) \]

不合法的方案数就是

\[\sum_{j> k} f[i][j][k] \]

\(g[i][j]\) 表示考虑到第 \(i\) 种烹饪方式,使用了 \(j\) 种食材时的总方案数,转移为

\[g[i][j]=g[i-1][j]+\text{sum}[i]\times g[i-1][j-1] \]

于是

\[\sum_{i=1}^{k} g[n][i] \]

就是我们的总方案数

综合来看,我们目前的时间复杂度是 \(\mathcal O(n^3m)\) 的,无法通过本题,于是考虑优化

这就是这道题最经典的地方了,我们发现对于 \(j,k\) 这两维,我们最后统计答案的时候关心的仅仅只是它们的相对大小关系,因此我们更改状态为 \(f[i][j]\) 表示考虑到第 \(i\) 种烹饪方式,钦定的使用数量最多的那一种食材相对其它食材的总和的差为 \(j\),那么转移就变成了

\[f[i][j]=f[i-1][j]+f[i][j-1]\times a[i][c]+f[i-1][j+1]\times (\text{sum}[i]-a[i][c]) \]

于是时间复杂度就降为了 \(\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}\) ,那么就有

\[\text{ans}=\sum_{i=m}^n(-1)^{i-m}\binom{i}{i-m}f[n][i] \]

时间复杂度 \(\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}\)

所以,对于本题,

\[\min(S)=\frac{1}{\sum_{i\in S}p_i} \]

我们套用 \(\text{min-max容斥}\) 的公式就可以过掉这道题了

时间复杂度 \(\mathcal O(2^n)\)

ARC101E Ribbons on Tree (容斥原理套路2)

题目

给定一个 \(n\) 个点的树,\(n\) 为偶数,点与点之间两两配对,配对点之间的边被覆盖,问有多少种配对方式,使得每一条边都有被覆盖

\(n\le 5000\)

题解

直接计算是 \(\mathcal O(n^3)\) 的,无法通过本题

所以考虑容斥,设 \(S\) 表示没有被覆盖的边的集合,将这些视作断掉的边,那么就有

\[\text{ans}=\sum_{S\subset E} (-1)^{|S|}f(S) \]

其中 \(f(S)\) 表示不能覆盖到 \(S\) 中边的方案数,那么答案就是

\[\text{ans}=\sum_{S\subseteq E}(-1)^{|S|}f(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\) 的方案数,转移有

\[f[i]=\text{cnt}[i][0]-\sum_{j=1}^{i-1}f[j]\times \text{cnt[j][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\) 的方案数,那么考虑容斥,有

\[g(n)=\sum_{i=0}^k(-1)^i\binom{k}{i}\binom{n-(x+1)i+k-1}{k-1} \]

组合意义就是对大于 \(x+1\) 的数的个数进行容斥,后面那一部分考虑整体加一后插板

于是我们要求的答案就变成了

\[\begin{aligned} &\sum_{i=0}^{kx}f(i)g(i)\\ =&\sum_{i=0}^{kx}f(i)\sum_{j=0}^{k}(-1)^j\binom{k}{j}\binom{i-(x+1)j+k-1}{k-1}\\ =&\sum_{j=0}^{k}(-1)^j\binom{k}{j}\sum_{i=0}^{kx}\binom{i-(x+1)j+k-1}{k-1}f(i)\\ =&\sum_{j=0}^{k}(-1)^j\binom{k}{j}\sum_{l=0}^{k-1}c_{j,l}\sum_{i=0}^{kx}f(i)i^l \end{aligned} \]

其中

\[c_{j,l}=[i^l]\binom{i-(x+1)j+k-1}{k-1} \]

这个东西显然是可以 \(\mathcal O(k^4)\) 暴力做的

这样我们的问题就转化为求

\[\sum_{i=0}^{kx}f(i)i^l \]

我们参考以往数位dp的经验,对上面那个式子进行转移,设 \(\text{dp}[j]=\sum_{i=0}^{kx} f(i)i^j\) ,假设我们枚举当前位 \(\text{now}\) 的取值为 \(x\) ,加完上一位时得到的数位 \(\text{num}\) 那么有转移

\[\text{dp}[a+b] \leftarrow f(\text{num}+x\times 10^{\text{now}})\times (\text{num}+x\times 10^{\text{now}})^{a+b} \]

即从

\[\begin{aligned} &f(\text{num}+x\times 10^{\text{now}})\times (\text{num}+x\times 10^{\text{now}})^{a+b}\\ =&\binom{a+b}{a}\times f(x)\times f(\text{num})\times \text{num}^b\times (x\times 10^{\text{now}}) ^a\\=&\binom{a+b}{a}\times f(x)\times \text{dp}[b]\times (x\times 10^{\text{now}}) ^a \end{aligned} \]

转移而来
最后注意 \(\binom{n}{m},(n< m)\) 时是 \(0\) ,而我们在算系数的时候会把这玩意算负,所以最后那个式子改成

\[\sum_{j=0}^{k}(-1)^j\binom{k}{j}\sum_{l=0}^{k-1}c_{j,l}\sum_{i=j(x+1)}^{kx}f(i)i^l \]

时间复杂度 \(\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}\)序列,每个数都有一个出现次数的全排列显然是多重组合数,所以方案数有

\[\frac{(n-2)!}{\prod(d_i-1)!} \]

因此我们最终所要求的东西就是

\[\sum_{\sum d_i=2n-2}\frac{(n-2)!}{\prod(d_i-1)!}\prod d_iw_i^{d_i}\\=(n-2)!\sum_{\sum d_i=2n-2}\frac{\prod d_iw_i^{d_i}}{\prod(d_i-1)!} \]

\(f[i][j]\)表示考虑到第\(i\)个数,度数和为\(j\)的方案数,枚举当前位的度数\(k\),转移就乘上一个\(\frac{kw_i^{k}}{(k-1)!}\),这部分可以预处理出来

所以暴力转移的复杂度是\(\mathcal O(n^3)\)

考虑使用多项式的幂公式,即

\[(x_1+\cdots+x_t)^n=\sum_{\sum t_i=n}\binom{n}{t_1,t_2,\cdots t_n}\prod x_i^{t_i} \]

我们令\(a_i=d_i-1\),那么有

\[\begin{aligned} \text{原式}&=(n-2)!\sum_{\sum a_i=n-2}\binom{0}{a_1,a_2,\cdots,a_n}\prod (a_i+1)!w_i^{a_i+1}\\&=(n-2)!\prod w_i\sum_{\sum a_i=n-2}\binom{0}{a_1,a_2,\cdots,a_n}\prod (a_i+1)!w_i^{a_i} \end{aligned} \]

现在我们离化简只差\(\prod (a_i+1)\)这个东西了,考虑它实际上是在枚举子集(枚举\(a_1\sim a_n\)几个相互组成的单项式),我们考虑当前我们枚举到的单项式是\(a_2a_3a_7\),那么这个单项式对答案的贡献为(注意单项式被乘进了分母里,所以对应的加和也要减\(3\)),前面\((n-2)!\prod w_i\)就先不管了

\[w_2w_3w_7\frac{1}{(n-5)!}\sum_{\sum a_i=n-5}\binom{n-5}{a_1,a_2-1,a_3-1,\cdots,a_n}w_i^{a_i}\\=w_2w_3w_7\frac{1}{(n-5)!}(w_1+\cdots+w_n)^{n-5} \]

所以类比到整个式子就变成了

\[(n-2)!\prod w_i \sum_{k=0}^{n-2}(\sum_{从n-2个里面选k个}\prod w_i)\frac{(w_1+\cdots w_k)^{n-2-k}}{(n-2-k)!} \]

\(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\) ,那么转移方程即为

\[f_{i+1,j+d,(k+i\times d)\bmod p}\leftarrow f_{i,j,k}\times g_{i,d} \]

考虑如何求出 \(g_{i,j}\) ,首先通过找 \(10^k\)\(p\) 的循环节,我们很容易算出来每个剩余类的大小为多少,记作 \(\text{cnt}_i\) ,考虑我们现在的贡献 \(j\) 都是由剩余类 \(i\) 的哪些位置组成的,这里可以插板法算,也就是把 \(\text{cnt}_i\) 个板插到 \(j\) 个位置上,板 \(k\) 和板 \(k+1\) 之间的数的个数就是第 \(k\) 个位置所产生的贡献大小,这里的方案数即为。

\[\binom{\text{cnt}_i+j-1}{j} \]

注意到一个位置的贡献最高是 \(9\) ,所以我们考虑容斥,钦定一些位置的贡献至少是 \(10\) ,钦定了 \(k\) 个位置,系数就是 \((-1)^k\binom{\text{cnt}_i}{k}\) ,然后还要把 \(j\) 减去 \(10k\)

然后由于 \(\text{cnt}_i\)\(n\) 同阶,所以我们算上面的组合数需要用到下降幂,即

\[\binom{n}{m}=\frac{n^{\underline m}}{m!} \]

总时间复杂度即为 \(\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\) 的组的个数,那么每次的转移系数就为

\[\binom{j+\text{cnt}_i}{k\times i}\binom{k\times i}{i,i,\cdots i} \]

第二个组合数下面有 \(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\) 表示括号序列的个数,那么有转移方程

\[f_n=2k(g_{n-1}-\sum_{i=1}^{n-1}\dfrac{f_i}{2k}\times \dfrac{g_{n-i}}{2k}) \]

\[g_n=\sum_{i=1}^nf_i\times g_{n-i} \]

第一个式子考虑容斥,总方案数 \(g_{n-1}\) 减掉不合法的方案即为合法方案(无切分)。枚举切分点 \(i\) ,钦定前 \(i\) 个一定合法(不可切分),后 \(n-i\) 个随便填,注意两端括号选取的总方案数为 \(2k\) ,这样就可以算出不合法方案数

第二个式子同样考虑枚举切分点

接下来考虑使用 GF 技巧来快速计算上面的式子

我们设 \(F\)\(G\) 分别对应 \(f\) 数组和 \(g\) 数组的多项式,那么我们可以列出下面两个式子

\[F=1+2kxG-\dfrac{1}{2k}(F-1)(G-1) \]

\[G=\dfrac{1}{2-F} \]

第一个式子乘 \(x\) 是因为要整体右移一位,减 1 减去的是常数项

第二个式子考虑一个括号序列可以由多个不可切分的括号序列组成,所以式子形如

\[G=1+(F-1)+(F-1)^2+\cdots \]

然后这个东西的封闭形式是经典的 \(\dfrac{1}{1-x}\) ,把 \(F-1\) 套进去就行

然后通过我们高超的初二数学技巧,我们解出来

\[G=\dfrac{k\sqrt {1-(8k-4)x}+1-k}{1-4k^2x} \]

\(1-k\)\(k\) 就是一个只有常数项的单项式,\(\dfrac{1}{1-4k^2x}\) 就是数列 \(p_n =(4k^2)^n\) 所对应的多项式的封闭形式,这样我们只要把封闭形式为 \(\sqrt {1-(8k-4)x}\) 的多项式的系数给求出来即可

然后这里题解给了一个很微妙的类似于求解常微分方程的做法,具体来说,设

\[H=\sqrt {1-(8k-4)x} \]

\(H\) 求导得

\[H'=\dfrac{4-8k}{2\sqrt {1-(8k-4)x}} \]

分母有理化得

\[H'=\dfrac{(2-4k)H}{1-(8k-4)x} \]

然后考虑第 \(n\) 项的系数, \(H'\) 就相当于是将 \(H\) 整体左移一位再乘上一个指数,也就是 \((n+1)h_{n+1}\),右边分母的 \(x\) 乘到左边就相当于整体右移一位,即 \(nh_{n}\) ,所以根据上面那个式子可以建立 \(h_n\)\(h_{n+1}\) 之间的方程:

\[(n+1)h_{n+1}-(8k-4)nh_n=(2-4k)h_n \]

得到

\[h_{n+1}=\dfrac{8kn-4n-4k+2}{n+1}h_n \]

这样这道题就做完了,由于只用求 \([x^n]G(x)\) ,所以时间复杂度 \(\mathcal O(n)\)

代码里懒得线性求逆元了,所以复杂度是 \(\mathcal O(n\log n)\)

posted on 2023-10-27 18:07  star_road_xyz  阅读(28)  评论(0编辑  收藏  举报

导航