指数型生成函数的范例——状压dp、矩阵快速幂优化及对指数型生成函数的理解

从《组合数学》中的经典例题开始。

在n个排成一排的瓷砖上染色,有4种颜色,有2种要求出现次数为2的倍数,其他的无要求。求方案数,模998244353。

组合计数问题先看能否dp。显然可以考虑“前缀定义dp”,于是我们还要考虑当前这个颜色对每种有要求的颜色的奇偶性的影响。那么定义dp[i][S]为涂了前i个瓷砖,两种颜色出现次数各自模2的状态位为S的答案。则答案为dp[n][0]。转移分2种情况:

  • 当前涂了无要求的颜色,贡献为dp[i-1][S]
  • 当前涂了有要求的颜色,设是第j种有要求的颜色,j=0~1,贡献为dp[i-1][S^(1<<j)]。模2加法(减法)和异或等价,因此恰好可用异或表示。
代码
N,mod = int(1e5)+5,998244353
kind = 4
dp = [[0 for j in range(4)] for i in range(N)]
dp[0][0] = 1
for i in range(1,N):
    for S in range(4):
        dp[i][S] = (kind-2)*dp[i-1][S]%mod
        for j in range(2):
            dp[i][S] = (dp[i][S] + dp[i-1][S^(1<<j)]) % mod
ans = [dp[i][0] for i in range(N)]
print(ans[1:31])
for i in range(1,31):
    assert ans[i] == (4**(i-1) % mod + 2**(i-1) % mod) % mod

因为i只用到i-1,所以显然还有一个矩阵快速幂的优化,设转移矩阵为M:

M[S][S] += (kind-2)
M[S][S^(1<<j)] += 1

代码略,太难写了QAQ

指数型生成函数

普通生成函数解决的是拿出元素即可的“组合问题”,指数型生成函数只是在上述问题的基础上,多了一个排列的步骤,这也是为什么说它解决的是“排列问题”。设每个分组(注:普通生成函数可以看成分组背包+染色过程,系数是分组内某物品的染色方案数,令系数为0、1则可以看成分组背包,一个函数表达了一个分组的所有可选方案。)取了i[1],i[2],……,i[m]个,i[1]+i[2]+……+i[m]=n,则这个方案对排列方案数的贡献是
n ! ∏ j = 1 m i [ j ] ! \frac{n!}{\prod_{j=1}^{m} i[j]!} j=1mi[j]!n!
n!是常量,扔出贡献的求和式,剩下分母的乘积,在乘法意义下是可以分开的(假如不能分开,就束手无策了~),所以考虑对普通生成函数做些修改,能够表示原问题的答案。显然把原序列的a[i[j]]改成a[i[j]] / [i[j]]!,就很接近答案了,我们把这一修改所得到的函数叫做指数型生成函数。为了统一形式,我们把刚刚扔出的n!除到ans变量这边。

这时我们就直观地看到了指数型生成函数这一定义的和谐:若干个指数型生成函数乘起来,取x^n项系数,乘上n!即可得到原问题的ans。

例子

如果你没看懂以上口胡,还可以看这个例子:记fg是两个“普通生成函数”的数组,表示物品的两个分组,f[i]表示取分组f的价值为i的物品,该物品染色方案有f[i]种(注:f[i]=0表示该物品不存在),g[i]表示取分组g的价值为i的物品,染色方案有g[i]种。则枚举分组f的价值i得(n表示规定的总价值)
h n = ∑ i = 0 n C n i ∗ f i ∗ g n − i h_n=\sum_{i=0}^nC_n^i*f_i*g_{n-i} hn=i=0nCnifigni
C(n,i)是以上可重集排列公式的一个特例。两边除以n!就再次得到了arr[c] / c!的统一形式。两个指数型生成函数可以乘起来而保持性质,根据数学归纳法,任意多个乘起来也能保持。我们又一次看到了指数型生成函数这一定义的合理性。

推倒

接下来推导以上例题的通项公式。取序列为全1,则得到的生成函数就是e^x,则两种无限制的颜色对应的指数型生成函数是e^x。取序列为偶数次幂项为1,其他项为0,记这个函数为f(x),则所需生成函数是
e x ∗ e x ∗ f 2 ( x ) e^x*e^x*f^2(x) exexf2(x)
f(x)可以轻易地由e^x得到:
f ( x ) = e x + e − x 2 f(x)=\frac {e^x+e^{-x}} {2} f(x)=2ex+ex
则答案为([x^n]表示取x^n项系数)
n ! ∗ [ x n ] e 4 x + 2 ∗ e 2 x + 1 4 n!*[x^n]\frac{e^{4x}+2*e^{2x}+1}4 n![xn]4e4x+2e2x+1
代入泰勒展开式得通项公式
a n s [ n ] = 4 n − 1 + 2 n − 1 ans[n]=4^{n-1}+2^{n-1} ans[n]=4n1+2n1

一个稍难的例题

在n个排成一排的瓷砖上染色,有5种颜色,有2种要求出现次数为2的倍数,另有2种要求出现次数是3的倍数,其他的无要求。求方案数,模998244353。

一般形式的问题,状压dp是那种“乘积”进制的,这里为了方便,定义第0 ~ 1、2 ~ 3种颜色分别要求出现次数是2、3的倍数。最难实现的部分就是要对第j位减1(如果刷表法就是要加1,实现起来一样困难),可以参考我萌新的实现。

N,mod = int(1e3)+5,998244353
kind,kindw = 5,4
tot = [1]*(kindw+1)
up = [2,2,3,3]
for i in range(1,kindw+1):
    tot[i] = tot[i-1] * up[i-1]
dp = [[0 for j in range(36)] for i in range(N)]
dp[0][0] = 1
pre = lambda S,i: (S-tot[i]) if S//tot[i]%up[i] > 0 else (S+(up[i]-1)*tot[i])
for i in range(1,N):
    for S in range(tot[kindw]):
        dp[i][S] = (kind-kindw)*dp[i-1][S]%mod
        for j in range(kindw):
            dp[i][S] = (dp[i][S] + dp[i-1][pre(S,j)]) % mod
ans = [dp[i][0] for i in range(N)]
print(ans[:31])

n=0~30的答案:[1, 1, 3, 9, 29, 121, 485, 2171, 10321, 48771, 244763, 1233871, 6281291, 32063669, 162984643, 826409229, 181270149, 55312336, 774146648, 374461405, 68458901, 948364094, 940175290, 676426379, 740453442, 106253853, 658215385, 361825022, 202182360, 323864789, 850995624]。只是过了对拍,如果有错请告诉我QAQ

指数型生成函数

取序列为3的倍数次幂项为1,其他项为0,记这个函数为f(x)。这玩意我没找到用e^{kx}表示的方式,所以直接手打了多项式乘法,仅做对拍演示

N,mod = int(1e2)+5,998244353
fac = [1]
ifac = [0 for i in range(N)]

def q_pow(a,b,mod):
    ret = 1
    while b:
        if b&1: ret = ret * a % mod
        a = a * a % mod
        b >>= 1
    return ret

def init():
    global fac,ifac
    for i in range(1,N): fac.append(fac[i-1] * i % mod)
    ifac[N-1] = q_pow(fac[N-1],mod-2,mod)
    for i in range(N-2,-1,-1): ifac[i] = ifac[i+1] * (i+1) % mod

def mul(a,b):
    n = len(a)+len(b)-1
    ret = [0 for i in range(n)]
    for i in range(n):
        for j in range(i+1):
            if j >= len(a) or i-j < 0 or i-j >= len(b): continue
            ret[i] = (ret[i]+a[j]*b[i-j]%mod)%mod
    return ret

init()
res = [ifac[i] for i in range(N)]
e2 = [((i&1)^1)*ifac[i] for i in range(N)]
e3 = [ifac[i] if i % 3 == 0 else 0 for i in range(N)]
for _ in range(2): res = mul(res,e2)
for _ in range(2): res = mul(res,e3)
ans = [res[i]*fac[i]%mod for i in range(31)]
print(ans)

如果是像上面这种找通项公式不方便的情况,矩阵快速幂就成为获取n=1e18的答案的唯一手段了

呜呜呜,还是因为太菜

  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值