zoukankan      html  css  js  c++  java
  • 数数专题

    注:尚未完成,先发出来,然后去睡觉

    数数,是一个从出生就开始学并一直学不会的东西

    这里讲“数数”,是指:形如“计算满足xxx条件的方案数”的问题。

    实现较复杂的问题会给代码。剩下的可以在 vjudge/原网站 上翻我的提交记录得到代码

    注:我一般喜欢交vjudge,并且我的常用号是 LightningUZ_CF 而不是 LightningUZ

    有一些数数,比如 GF 相关,会另外讲。

    这里讲的主要是不用GF,用组合数,dp等技巧的数数题。

    借助dp

    动态规划是数数的好朋友,很多时候我们可以用动态规划解决数数问题。

    有人称其为 “计数dp”。比如我们熟悉的背包,就是一种dp。

    当然,和背包相关的dp,与数数的关系不大,不细展开。

    来点例题:

    CF814E

    参考了LCA的做法,他的题解在 这里

    LCA的题解太神仙了,导致我这个菜鸡几乎看一行式子睡一次觉,就为了想 “这里为啥-1?” 这种事情,睡了4、5觉才做出来

    所以这里会仔细讲(

    这题是比较纯正的dp,只用到了非常少量的组合数学内容,全他妈在讨论

    我们发现这条件还挺多:最短路唯一,最短路递增,度数有限制,并且为 (2)(3)

    “最短路递增”,想象一下最后的最短路数组,相同的应该连续。那我们可以把连续的一段相同的划开,称为 “段”。

    注意到,相邻两个段的最短路应该正好相差 (1)。并且后面一段的每个点都会有 恰好一条 到上一段的边。

    (如果没有,那最短路就不对;如果有两条以上,那最短路就不唯一)

    段内部允许有边。

    看到分段,很容易想到dp。虽然粗略一想似乎不太会,但我们不用急,慢慢来。

    别的不管,先设 (f(i)) 表示分到第 (i) 段的方案数,这个状态怎么也得有。

    然后我们按套路,枚举上一段的点 (j),然后 (f(j) imes w(j+1,i)) 转移过来。(w) 函数表示转移系数。

    仔细一想,这个转移好像和段里面有多少个点有很大的关系。那我们得再记一维表示点数。

    (f(i,j)) 表示到第 (i) 段,最后一段分了 (j) 个点,方案数。

    (f(i,j)=sumlimits_{k} f(i-j,k) imes w(...))

    我们注意到这样还不太能做,因为 (2) 度点和 (3) 度点处理起来不太一样,所以这个 (w) 似乎不太能写出来。

    那就记一下上一层有多少个 (2) 度和 (3) 度的点,记为 (c_2,c_3),然后考虑它们在自己那一层(即,上一层)内部,以及和当前的这一层,如何匹配起来。

    上一层的点数可以直接得知,就是 (c_2+c_3)。这一层的点数需要再记一下。

    再一想,上一层的每个点到上上层都恰好有一条边。那这么说,(2) 度点其实只有 (1) 个插头,然后 (3) 度点有 (2) 个插头。所以转移函数的本质是要考虑这些 插头的匹配

    于是我们设这个转移的函数为, (g(n,c_2,c_3)) 表示:

    • 这一层有 (n) 个点,上一层有 (c_2) 个点有 (1) 个插头, (c_3) 个点有 (2) 个插头;
    • 上一层的更前面已经满足了限制,并且上一层到上上层的边已经连好了;
    • 我们要考虑,上一层内部的匹配,以及上一层与下一层的匹配;

    的方案数。

    容易写出转移:(f(i,j)=sumlimits_{k} f(i-j,k) imes g(j,c_2,c_3))(c_2,c_3) 很容易统计出来)

    最后的答案就是 (sumlimits_{j} f(n,j) imes g(0,c_2,c_3))

    那我们把 (g) 搞出来就做完了。但是这个 (g) 看起来比较复杂,分情况讨论

    以下称 “(A)” 表示只有一个插头的点,(B) 表示有 (2) 个插头的点

    1. (n=0)

      此时下一层没有点,那就只需要考虑层内的匹配

      1. (c_2=0)(c_3>0)

        此时只有 (B) 点,那这样匹配一波,一定会形成若干个环。

        我们枚举其中一个环来转移。

        为了避免算重复,我们强制让枚举的那个环包含 (B) 点中标号最小的那个。

        由于不能有重边和自环,环长至少为 (3)

        得到转移: (g(0,0,c_3)=sumlimits_{i=3}^{c_3} N(i) imes inom{c_3-1}{i-1} imes g(0,0,c_3-i))

        其中 (N(i)) 表示 项链数,也就是排一个环的方案数。

        组合数里面的 (-1) 是因为我们已经选好一个,接下来只要选 (i-1)

      2. (c_2,c_3>0)

        此时 (A,B) 点都有。我们考虑连一条边,这条边可能连接着:

        (A+A)(B+B)(A+B)

        注意到我们不应该考虑 (B+B),因为它要么会在 (g(0,0,*)) 的时候被算到,要么会在考虑完 (A,B) 插头间的边之后,其中一个 (B) 变成 (A)

        所以我们只需要考虑,一个 (A) 点和哪种点匹配了

        为了避免重复,还是要强制选 (A) 里面编号最小的那个。

        如果我们再选一个 (A),那就有 (c_2-1) 种选法,并且两个插头都接起来,没了。这一部分是 ((c_2-1) imes g(0,c_2-2,c_3))

        如果我们选一个 (B),那就有 (c_3) 种选法。而此时,(A) 点插头没了,而 (B) 点的插头数从 (2) 个变成 (1) 个,也就便乘了 (A) 类点。于是 (A) 类点的数量并没有变化,只有 (B) 类点的数量少了一个。那这一部分是

        (c_3 imes g(0,c_2,c_3-1))

        综上,(g(0,c_2,c_3)=(c_2-1) imes g(0,c_2-2,c_3)+c_3 imes g(0,c_2,c_3-1))

    2. (n>0)

      此时我们需要考虑下一层的匹配

      下一层的每个点都要找一个匹配。为了避免重复,我们考虑下一层里面标号最小的那个点和谁匹配。

      它可能和 (A) 类点匹配,有 (c_2) 种选法,此时 (A) 类点少一个,(B) 类不变。即 (c_2 imes g(n-1,c_2-1,c_3))

      它可能和 (B) 类点匹配,有 (c_3) 种选法。此时有一个 (B) 类点变成 (A) 类点,所以是 (B) 少一个 (A) 多一个,即 (c_3 imes g(n-1,c_2+1,c_3-1))

      综上,(g(n,c_2,c_3)=c_2 imes g(n-1,c_2-1,c_3)+c_3 imes g(n-1,c_2+1,c_3-1))

    注意边界 (g(0,0,0)=1)

    然后就可以先预处理出 (g),然后对着式子搞 (f),然后对着式子搞答案。

    总的复杂度是 (O(n^3)),非常显然。

    代码

    AGC24E

    这题是一个“计树dp”,通常有一种套路:(f(i,...)) 表示 (i) 个点的树,...,的数量。

    不过这题,要搞出这颗“树”要花一些功夫。

    首先考虑一个问题,题目问的是本质不同的 序列组数量,而同一种序列组可能有不同的插入方案,如何去重?

    我们发现,重复当且仅当我们插入的数有一堆连续的重复。比如aaabb要变成aaabbb。

    此时我们强行插在最后一个位置,然后方案就是唯一的了。

    由于我们要让字典序递增,再分析一下这种插入方式的性质,我们发现:每次插入进去的元素,要比“顶掉”的元素的字典序 严格 大。

    为了避免边界情况,我们令序列的最后有一个 (0),并且我们不能插在 (0) 后面。那插入在最后的情况相当于是顶掉了 (0),没问题。

    我们考虑每个数的 “流向”。即,我们画一根线。当它被插进来的时候,就新建一个线头。如果它没有被顶掉,那这根线竖直向下,否则就斜着向右下。

    比如说,序列组:

    0
    1 0
    1 1 0
    1 1 1 0
    1 1 4 1 0
    1 1 4 5 1 0
    1 1 4 5 1 4 0
    

    它的 “流向” 示意图如下:

    image-20210824203724613.png

    每个红色的圈圈表示一个线头的开始。用眼睛瞪这个图,结合脑子想,发现:

    • 0一定是一条斜线向下
    • 每一个 “线头” 一定比它上面的那个位置 严格 大(因为它要顶掉上面那个位置)

    然后再一想,发现:我们可以把每个(0以外的)线头和它上面那个位置所在的线头连一条边。这样一定是颗树(因为“严格大于”这个东西不可能有环,并且显然连通)。我们考虑以 (0) 为根。

    这颗树会有很多个叉,但是它的点权值在树上外向递增(即,父亲小于儿子)

    考虑每个“线头”被插入进来的时间,我们发现这玩意很显然也递增。

    那我们给树上的每个点两个权值,一个表示插入时间,一个表示填的数字。我们发现,如果知道了每个点的插入时间,也知道它的父亲,那我们其实就能唯一确定它去顶掉哪个点,再结合填的数字...我们就能唯一确定一个方案!(即,一个序列组)

    于是变成了数树问题。

    考虑到俩权值要递增,我们设 (f(n,x)) 表示,(n) 个点的树,根节点权值是 (x),方案数。答案是 (f(n+1,0))

    考虑根的所有子树,发现每个子树的根的权值都 (ge x+1),并且一共有 (n-1) 个点。这是一个森林计数。

    而我们发现“森林计数”和“树计数”可以互相转移!不妨换一下状态,设:

    • (f(n,x)) 表示 (n) 个点的森林,每个树的根的权值都 (ge x),方案数

    • (g(n,x)) 表示 (n) 个点的树,根的权值是 (x),方案数。

    首先,(g(n,x)=f(n-1,x+1...k))。我们用后缀和优化掉这个东西。

    然后 (f(n,x)=sumlimits_{i=1}^{n} f(i,x) imes g(n-i,x) imes inom{n}{i})

    这个很明显,我们选出前面 (i) 个是森林,然后剩下的 (n-i) 个单独构成一颗树,就行了。

    (inom{n}{i}) 是因为我们要给它分配一个 “插入时间”。前面的 (f,g) 这样合并不会算重是因为,虽然它会算重树结构,但是此时的插入时间那一维不会重。

    注意到这样的 (f) 是至少两棵树的。我们给它加上 (g(n,x)) ,就把独木成林的情况考虑到了。

    然后就这样转移一波即可,答案是 (g(n+1,0))

    AGC16F

    好家伙,刚数完树,开始数DAG了

    先拿SG函数做一波转化,再考虑反面,相当于要数 (SG(1)=SG(2)) 的方案数。

    然后我们考虑按 (SG) 的值把图分层。

    考虑把 (S)(SG=0) 的那些点抽出来,发现:其余每个点到它们至少一条边,并且这些点内部不能有边。

    抽出来之后,把它们删掉,发现剩下的点里面,整体 (SG)(-1),满足同样的性质,即,最优子结构。

    于是考虑dp。设 (f(S)) 表示 (S) 中最小的两个元素 (SG) 值相同的情况,(U) 表示全集,认为是 (1...n) 组成的集合。然后答案就是 (2^m-f(U))

    注意到 (S) 不能把 (1,2) 分开,否则这个dp状态就不可用。然后枚举 (S) 的子集 (T),记 (S/T) 表示 (Scap (overline{T})),即 (S) 去掉 (T) 的那一部分。

    那么 (S/T) 中每个点到 (T) 至少一条边 (和上题挺像,少了度数限制),(T)(S/T) 的边可以瞎连,不影响。

    (T) 内部的边 全部不能连 ,而 (S/T) 内部的连边通过 (f) 得到。

    那就是 (f(S)=sumlimits_{Tsubsetneqq S} f(S/T) imes w_1(T,S/T) imes w_2(S/T,T))

    (E(u,S)) 表示 (u) 有多少出边到 (S)。可得转移函数:

    (w_1) 表示瞎连方案数,(w_1(S_1,S_2)=prodlimits_{xin S_1} 2^{E(x,S_2)})

    (w_2) 表示连至少一条边的方案数,把 (w_1) 的转移变成 (prod (2^{...}-1)) 就行了,很明显 (-1) 就可以去掉不连的方案。

    这两个都可以每次预处理出 (E) 之后,扫一遍算出来。

    于是总复杂度是 (O(n imes 3^n))

    小结

    我们可以先研究一波问题的性质,把问题转化成好做的形式,或者是提炼出我们真正需要知道的东西,再用dp做。

    (接下来就开始飞了)

    dp+复杂度平衡: loj547

    首先考虑一个naive dp:

    (f(i)) 表示长度为 (i) ,满足条件的01串个数。那么: (f(i)=2f(i-1)-f(i-m-1))

    这玩意有两种搞法:

    1. 注意到 65537 是个 NTT 模数,对于 (mle 8192) 的数据(我实现的不太精细,如果精细实现可以做到 (mle 32768)),可以跑一个常系数齐次线性递推,(O(mlog mlog n))
    2. (m>8192) 的时候,注意到 (dfrac{n}{m}) 非常小。我们给这个递推赋予组合意义:(u)(u+1)(2) 条重边,(u)(u+m+1)(-1) 条重边,求方案数。我们枚举跳了多少条 (+(m+1)) 的边,计算出跳了多少条 (+1) 的边,然后用组合数瞎jr算一下方案数加起来就行;复杂度 (O(dfrac{n}{m}))

    综合两种做法,就可以过了。

    注意一个细节:做法1的递推里面,边界是:当 (i<m) 时,(f(i)=2^i)(f(m)=2^m-1)

    (f(m)=2^m-1) 是我们手动设置的,如果按做法2直接跑,得到的是 (f(m)=2^m)。容易发现,只有这一位不对。

    如何把它修正回来?(修正主义

    设做法2跑一个 (n) 得到的方案是 (g(n)),也就是在DAG上从 (0) 跑到 (n) 的方案数。

    一个很显然的想法是,我们直接跑这个计数,相当于是给 (f(m)) 加了 (1) 之后做上面的递推。不合法的部分,就是这个 (f(m)) 增加 (1) 之后的影响。考虑组合意义,这个影响就相当于在 DAG 上从 (m) 走到 (n) 的路径数。由于这个DAG的边只和点的相对位置有关,所以这个方案数就是 (g(n-m))

    所以我们得到 (f(n)=g(n)-g(n-m))

    注意到 (g(n+1)=2g(n)-g(n-m)),所以这个式子也等于 (g(n+1)-g(n))

    不过我没想明白它的组合意义。评论区大佬也可以讲一下,如何形象一点的理解 (g(n+1)-g(n)=f(n)) 这件事情。

    dp+压缩自动机: CF506E

    众所周知,一些dp可以用自动机的语言写出来,转化成自动机上的数路径问题

    对于一个自动机,众所周知,我们可以压缩它

    有以下几种策略:

    • 转移边有很多重合部分,把它压起来,如SAM
    • 有用的状态数很少,如PAM (本质不同回文串 O(n) 个)
    • 根据问题特点,改写自动机,并按照上面两个方法压缩,如,本题

    先写一个 naive-dp。设 (m=|s|)(N) 表示串的总长,即 (n+m)

    (f(i,l,r)) 表示长度为 (i) 的回文串,使得 (s[l:r]) 是它子序列的方案数。

    每次考虑在串的两边加字符,并考虑它是 (s_l,s_r),还是其它。然后讨论一波:

    边界:

    • 如果 (i=0)(f(i,l,r)=[l>r])
    • 如果 (i=1)(f(i,l,r)=[lge r])
    • 如果 (l>r)(f(i,l,r)=26^{lceil frac{i}{2} ceil})

    转移:

    • 如果 (s_l=s_r)(f(i,l,r)=25f(i-2,l,r)+f(i-2,l+1,r-1))
    • 如果 (s_l eq s_r)(f(i,l,r)=24f(i-2,l,r)+f(i-2,l+1,r)+f(i-2,l,r-1))

    然后 (f(N,1,m)) 就是答案了。

    把状态 ((i,l,r)) 里面的那个 (i) 看成是在走步(每次走两步,分奇偶讨论下就行),并把 ((l,r)) 建点。

    如果 (l>r),我们称其为“结束点”,记作 (E) 类;如果 (s_l=s_r),称其为“相同点”,记作 (A) 类;否则称为 “不同点”,记作 (B) 类。

    我们把点排成方阵。(A,B) 构成一个上三角,(E) 都在左下角。

    对角线上一定都是 (A)

    一个 (A) 点,它可以向左下角连一条边;一个 (B) 点,它可以向正左/正右连一条边。

    每个点上都有自环。根据dp方程,(A) 点有 (25) 条自环,(B) 点有 (24) 条自环,(E) 点有 (26) 条自环。

    我们从 ((1,m)) 开始走,走到 (E) 点结束,可以走边,可以走自环,走 (N) 步,问方案数。

    暴力矩乘,(O(m^6log n)),过不去。

    我们考虑压缩这些点构成的自动机。注意到我们的 (E) 点只需要两条对角线,(j=i-1)(j=i-2),因为我们一定会走到这两条线之一就停止。

    根据这个,我们走一条路下来,(r-l+1) (长度) 肯定会从 (m) 变化到 (0/-1)。注意到,走一个 (A) 长度 (-2),走一个 (B) 长度 (-1)。设我们走了 (a)(A)(b)(B),那么 (a=(m-b+1)/2)

    也就是说,确定了 (b) 就可以确定 (a)

    然后我们注意到,一条路径的贡献,只和 (a,b) 有关,贡献是 (25^a imes 24^b imes 26/1),和经过的顺序并没有关系。

    (a,b) 值相同的路径可能有很多条,但是我们现在知道,本质不同只要O(m)条

    (P(b)) 表示经过了 (b)(B) 的路径条数。然后我们的自动机就只需要关心 (A,B) 的数量了。

    注意到我们怎么也得经过一个 (A),那么 (b) 的值域是 ([0,m-1])(a) 的值域是 ([1,(m+1)/2])

    我们建出这样的一个自动机:点 (B_i) 表示经过 (i)(B) 对应的状态,(A_i) 同理。

    为了让它俩能 “拼起来”,我们考虑把它俩对接,如下:

    (B_{m-1} o B_{m-2} o ... o B_{1} o A_{1} o A_{2} o ... o A_{(m+1)/2})

    每个 (A_i) 的下面再挂一个 (E_i) 表示结束。

    每个 (B) 点有 (24) 个自环,(A) 点有 (25) 个自环,(E) 点有 (26) 个自环。

    这样,枚举 (b),计算出 (a=(m-b+1)/2)(B_b)(E_a) 的路径就和原图上的 本质不同路径 一一对应了。

    走一步相当于长度 (+2)

    如果 (N) 是偶数,把这个图的邻接矩阵搞出来求个 (N/2) 次幂,统计一下答案就行了。

    如果 (N) 是奇数,稍微复杂些。

    我们令最后一步的长度只 (+1)。那我们会走 ((N+1)/2) 步。

    不合法,当且仅当从一个 ((x,x+1))(A) 类点转移到 (E)。因为它要做最后一步,但它需要加两个相同字符,而我们强制让最后一步只加一个字符,就不能真正的匹配上。

    我们把这种情况减掉即可。手动推一推,发现这种情况下有 (2a+b=m)。而且我们只能恰好走上 (E) 点,而不能在上面绕自环。

    这就等价于我们走 ((N+1)/2-1=(N-1)/2) 步,走到一个 (A) 类点。

    所以我们就把那个矩阵的 ((N-1)/2) 次幂算出来,枚举 (a),计算得 (b=m-2a),求 (B_b)(A_a) 的路径数量的和,就是不合法的方案数。

    不要忘记乘个 (P)

    代码

    容斥原理

    每个的容斥背后,都有一个默默支持它的反演

    常见的容斥,容斥系数是正负/正负+组合数的,本质是二项式反演

    容斥系数带 (mu) 的,本质是莫比乌斯反演,本质的本质是在质因数的集合上二项式反演,(mu) 是其容斥系数。

    二项式反演,可以搞出来一堆集合的交/并,或者把“恰好”转换成“至少”

    如,CTS2019 随机立方体

    容斥也可以结合dp,通过dp记录容斥的贡献来做

    如,CTS2019 氪金手游

    由于这两题都属于概率期望,所以这里不讲(雾)

    其实概率期望的本质也是数数

    只不过我懒得再写一遍了qaq

    去翻 “概率期望” 那篇罢

    注,莫比乌斯容斥和这个东西的关系不大,所以这里略

    naive题:51nod 1829

    考虑直接瞎jr射,方案数是 (n^n) ,但肯定会有不射满的 (戴黄看黄

    然后我们考虑容斥,容易想象到,我们应该这样做:

    射在 (n) 个数内 - 射在 (n-1) 个数内 + 射在 (n-2) 个数内...

    这样的容斥显然是对的。想象一下正负号,得到:答案为

    [sumlimits_{i=1}^{n} i^n imes (-1)^{n-i} imes inom{n}{i} ]

    可以用这个题来热热身。

    如果您对容斥比较熟悉,不用动笔,用嘴巴就可以搞出这个题了。

    反之,如果您用嘴巴切了这个题,说明您的容斥很强!

    ZJOI2016 小星星

    同样涉及到一个映射的问题,考虑和上个题类似的做法:我们先不保证射满,然后容斥。

    先枚举一个集合 (S),并限制我们必须射在这里面。然后做树形dp:(f(u,x)) 表示,(u) 射到 (x)(u) 子树射的方案数。

    枚举 (v) 射到了 (x'),那在原图上 (x)(x') 之间就要有边。如果有,就把这个方案加到 (f(u,x)) 里面。

    最后 (f(1,*)) 的和就是 (S) 的答案,容斥一下加起来即可。

    noiac2201 连续子序列: dp出容斥的贡献

    (noi.ac的题有权限,这里给出题意)

    称一个排列的子串为顺子,当且仅当:这个子串里两两之间差的绝对值为 (pm 1) (即,连续单增/单减)

    (n) 个数的排列中,有多少种排列满足它所有顺子的长度都 (le m)。模 (1e9+7)

    (mle nle 5000)

    考虑反面,我们枚举它有多少个长度为 (m+1) 的顺子(枚举左端点),其余位置任意选,设这个方案为 (c(i))。那么答案为

    (c(0)-c(1)+c(2)-c(3)...=sumlimits_{i}(-1)^i c(i))

    然后我们发现,两个顺子如果有交,那它们必须同时递增/递减,然后我们就可以把它合并成一个大块的顺子,称为一 “块”

    对于一个块,它里面选多少个长度为 (m+1) 的顺子,我们其实并不知道。但是我们只关心它们的方案数乘上容斥系数的和(因为这玩意就是答案)

    对于块之外的东西,每个位置都是独立的数,称为 “单点”

    假设我们有 (a) 个块,(b) 个单点,可以先安排出它们的相对顺序,((a+b)!) 种方案,然后就可以唯一确定它们里面填什么数了。

    对于一个块,它可以增或者减,那就还有 (2^a) 种分配方法。

    所以它们仅填数字的方案(即,不考虑结构和容斥系数)就是 (2^a (a+b)!) 种。

    (f(i,a,b)) 表示前面 (i) 个数,选了 (a) 个块,(b) 个单点,容斥贡献和。

    如果第 (i) 个位置是单点,那么 (f(i-1,a,b-1)) 贡献到 (f(i,a,b))

    如果第 (i) 个位置属于一个块,那就枚举一个 (j) 表示上一块的结尾,(f(j,...)) 贡献过来。

    发现这样并不对,因为我们不能直接用 (f) 贡献,因为 (f) 可能会有单点出来。

    那我们再搞一个 (g(i,a,b)) ,表示强制令 (i) 为一个块的结尾,的方案数。

    (f(i,a,b)=g(i,a,b)+f(i-1,a,b-1))

    考虑 (g) 的转移:

    • 如果当前选的顺子不和以前的重叠,贡献和是 (g(i-m-1,a-1,b))。由于多选了一个,容斥贡献就乘一个 (-1)
    • 反之,就和以往的某个顺子重叠了。那以前的顺子,它的右端点要在 ([i-m,i-1]) 里面,并且选了这个顺子之后,容斥系数乘 (-1),而块数不变,因为重叠了

    可得:(g(i,a,b)=-f(i-m-1,a-1,b)-g(i-m...i-1,a,b))。后面的 (...) 表示求和(这样写直观些)

    然后我们可以用前缀和优化掉这个东西,复杂度 (O(n^3))

    你开开心心的准备去写,一看数据五千。

    然后你发现这个转移其实和 (a+b) 的关系比较大,注意到每次 (a+b) 这个东西只会变化一个 (-1) 或者不变。

    但我们也不能只记一个 (a+b),因为 (a) 还要贡献一个 (2^a)

    那我们为什么不把这个 (2^a) 的贡献也搞进来呢?那就只需要记 (a+b) 了。

    我们记 (F(i,s)=sumlimits_{a+b=s} 2^a imes f(i,a,b))(G(i,s)) 同理。

    我们把 (F,G) 的sigma里面的 (f,g) 用上面的转移化开,整理,把它们也搞成 (F,G) 的形式。得到:

    (F(i,s)=G(i,s)+F(i-1,s-1))

    (G(i,s)=-2F(i-m-1,s-1)-G(i-m...i-1,s))

    为啥有一个 (2)?注意到那个 (a-1) 变成 (a) 的过程中,(2^a) 这个东西要变化的。

    然后注意到空间开不下,考虑滚动数组。

    接下来是一个 典 中 典: 如何滚 (i)

    注意到 (i) 这一维不太好滚。

    所以我们滚 (s)

    这里 是完整的事件(

    组合意义转化

    很多东西具有一个组合意义

    比如上面提到 (f(i)=2f(i-1)-f(i-m-1)) 的那个递推,它就可以看成DAG上的路径计数。

    同时,(n^m) 这个东西,也具有很好的意义:(n) 个数,选 (m) 次,选一个数之后不删除,方案数就是 (n^m)

    管道取珠

    对于第 (i) 种序列, (a_i^2) 相当于:取两次珠子,方案相同,且均为第 (i) 种序列的方案。

    那么我们取两次珠子,方案相同,方案数就是 (sum a_i^2)

    接下来就好做了,注意到数据范围非常小,设 (f(a,b,a',b')) 表示:第一次取 (A)(a) 个,(B)(b) 个第二次取 (A)(a') 个,(B)(b') 个,相同,的方案数。

    注意到 (a+b=a'+b'),故省去 (b'),然后滚一下就行了。

    AGC13E

    和上一题类似。一段长度的平方,就相当于在这一段里面选两个位置的方案数。

    而把这些平方乘起来,就相当于是在前 (i) 个里面选 (2k) 个格子的方案数,根据乘法原理。

    先不考虑障碍。设 (f(i,0/1/2)) 表示在前 (i) 个位置里面分段之后选,最后一段里面选了 (0/1/2) 个,的方案数。

    第一种是,在 (i-1,i) 这里划开一段,然后 (i) 这个位置把当前的 (j) 个格子全部选掉。这种方案是 (f(i-1,2)),前提是这一个位置不是障碍。

    第二种是,(i)(i-1) 在一段,考虑前面选了几个,然后 (i) 这个位置上选剩下的(可能为 (0))。枚举一个 (kle j),这样的方案数是 (inom{j}{k} imes f(i-1,k))

    于是,(f(i,j)=f(i-1,2)+sumlimits_{k=0}^{j} f(i-1,k) imes inom{j}{k})

    对于没被障碍隔开的一段,用矩阵优化转移。障碍特判一下就行。

    复杂度 (O(m imes 2^3 imes log n))

    noiac 2448,2327

    这两题的共同点是,需要求:长度为 (n) 的序列,每个数在 ([1,m]) 间。设 (L) 表示最长连续相同段,求 (L) 的分布(即,对于每个 (i),求 (L=i) 的方案数)

    注意到,恰好不太好做,但是“不超过”感觉挺能做。

    那我们现在要求最长连续相同段不超过 (x) 的方案,设为 (g(i))

    考虑 (i) 所在的那一段多长,有转移:(g(i)=(m-1) imes (i-m...i-1))

    注意一个细节:我们认为每一段都只能取 (m-1) 种,实际上第一段是可以取 (m) 种的。

    最后乘一个 (dfrac{m}{m-1}) 才是真正的答案。

    前缀和优化,设 (s)(g) 的前缀和。然后 (s(i)-s(i-1)=(m-1) imes [s(i-1)-s(i-m-1)])

    移项,得 (s(i)=m imes s(i-1)+(1-m) imes s(i-m-1))

    然后和上面的一个转化类似:把它看成DAG上的路径计数。

    枚举跳了多少次 (m+1),复杂度 (O(dfrac{n}{m}))。枚举 (m),这样算,复杂度是调和的。

    然后就得到了 (s)。取差分得到 (g)。然后发现 (g) 的差分就是 (L) 分布了。

    复杂度 (O(nln n))

    奇技淫巧

    有这样一个式子,合法方案书=瞎选方案数*正确率。

    假设我们能知道正确率,那问题就简单了。

    noiac 2034

    注意到 ((A,B)) 的方案可以和 (A,B) 拼起来的方案一一对应。

    现在问题变成一个序列:知道它有 (a)(1)(b)(-m)(和为 (s=a-mb),保证是正的),每个位置的前缀和都是正的,求方案数。设 (n=a+b) 为序列长度。

    考虑一个序列的所有循环同构,我们发现,它一共有 (n) 个循环同构,而其中的恰好 (s) 个满足前缀和都是正的。

    假设这个结论对,那就太好做了,答案是 (inom{a+b}{a} imes dfrac{s}{n})。这就是 “瞎选方案数”乘以“正确率”。

    证明如下

    对于一个序列,我们把它写无限遍。

    (las(x)) 表示,前缀和最后一次为 (x) 的位置。由于序列和为正,每过一个周期和就会增,所以这个 (las) 的值是有限的。

    对于 (iin [1,s]),考虑 ([las(i)+1,las(i)+n]) 这一段区间。根据 (las) 的定义,(las) 后面所有前缀和都 (>i)

    那这么说,这一段区间的前缀和都严格的大于 (i)。 考虑区间内部的前缀和,设 (pin [las(i)+1,las(i)+n]),则内部前缀和就是 ((las(i),p]) 的区间和,即 (s_p-i)。我们知道 (s_p>i),所以这个和大于 (0)

    那我们把这一段区间取出来,它的每个位置前缀和都 (>0),是这个序列的一个循环同构。

    所以我们有至少 (s) 个循环同构,使得它们任意位置前缀和 (>0)

    也容易发现,对于 (i>s),它取出来的这段序列和 (i-s) 是本质相同的,所以不能算。

    所以我们只有 恰好 (s) 个循环同构是满足条件的。

  • 相关阅读:
    1、编写一个简单的C++程序
    96. Unique Binary Search Trees
    python 操作redis
    json.loads的一个很有意思的现象
    No changes detected
    leetcode 127 wordladder
    django uwsgi websocket踩坑
    you need to build uWSGI with SSL support to use the websocket handshake api function !!!
    pyinstaller 出现str error
    数据库的读现象
  • 原文地址:https://www.cnblogs.com/LightningUZ/p/15188682.html
Copyright © 2011-2022 走看看