题目链接

一道好题,第一次用生成函数做题。感谢赛珂狼教我这个做法。

首先我们显然可以把题目中的限制转化成一个二分图的模型:左边有$n$个点,右边有$m$个点,如果在棋盘$(i,j)$这个点上放了炮,那么我们把左边第$i$个点向右边第$j$个点连边,那么最终这个图上左边每个点的度数都是$2$,右边每个点的度数都小于等于$2$。求合法图的个数。
可以发现,这个二分图是由一些环和一些链组成的,每一条链都是属于右边的点数比属于左边的点数多一(我们把属于右边的单独的一个点也算作一条链)。并且这样链的条数恰好为$m-n$。
对于每条链或者每一个环,一旦分配给它的点固定后,其内部的方案数就和外部无关了,只和该链(环)的大小有关。
我们先计算有关于链的部分。

我们定义函数$g_n$表示左边有$n-1$个点,右边有$n$个点的一条链内部排列的方案数。那就有:
$$g = <0, 1, 1, 6, \dots, \frac{n!(n-1)!}{2}>$$
简述上述式子的由来,左边$n-1$个点有排列$(n-1)!$,右边$n$个点有排列$n!$,乘起来之后左右对称要除以$2$。我们构造一个生成函数把$g$挂上去,表示有一条链的情况:
$$G(x) = \sum_{n} g_n \frac{x^n}{n!(n-1)!}$$
如果有两条链我们就把它自己卷一下,并且用$g^{[2]}$来表示新得到的数列:
$$G^2(x) = \sum_{n} \left( \sum_{i = 0}^{n} \binom{n}{i} \binom{n - 2}{i - 1} g_i g_{n - i} \right) \frac{x^n}{n!(n-2)!}$$
$$G^2(x) =\sum_{n} g^{[2]}_n \frac{x^n}{n!(n-2)!}$$
注意到这个生成函数的形式和$G(x)$相比有些许变动,即在$x^n$下的系数有改动,这是因为在两条链的时候右边的点数会比左边的点数多二,而不是一。于是更一般的,如果有$k$条链,就可以写成以下形式:
$$G^k(x) = \sum_{n} g^{[k]}_n \frac{x^n}{n!(n-k)!}$$
其中数列$g^{[m-n]}$就是我们想要的有恰好$m-n$条链的时候的方案数。但是细心的读者可以发现这里有一个小问题,就是我们在做卷积的过程中,我们每卷一次就会增加一条链,这相当于枚举了新加入的链的位置,于是在每一个合法的情况中,这$m-n$条链的加入顺序不同会被算成不同的方案,于是最终我们需要的应该是数列$\frac{g^{[m-n]}}{(m-n)!}$。
理解这一点很重要,我们在计算环的时候同样需要用到这一点,请大家自行揣摩。
我们知道了$G(x)$,于是我们只要求出$G^{m-n}(x)$就行了,直接快速幂可以$O(mlog^2m)$,如果比较厉害写$O(mlogm)$的多项式快速幂也是可以的。

接下来我们算环的部分。
我们定义函数$f_n$表示左右边都有$n$个点的环内部排列的方案数,那就有:
$$f = <0, 0, 1, 6, \dots ,\frac{(n!)^2}{2n}>$$
简单说明上述式子的由来,左右边分别有圆排列$(n-1)!$,合并起来有$n$中方案,同时镜像对称要除以$2$。同时,这里有特殊情况$f_1=0$,因为你不能有一个两个点的环。这里有读者会发现,我们定义了$f_0 = 0$,这是因为我们接下来做卷积时必须保证每次选择环的大小时不能用一个空环(也就是会导致实际环的个数会少一)。
同样我们构造了一个生成函数,表示一个环的情况:
$$F(x) = \sum_{n} f_n \frac{x^n}{(n!)^2}$$
与链同理,如果我们用了$k$个环,就有:
$$F^k(x) = \sum_{n} f^{[k]}_n \frac{x^n}{(n!)^2}$$
与链同理,每一种用了$k$个环的合法情况会被重复计算到$k!$次,我们要除掉它,同时我们需要枚举我们用的环的个数$k$,于是我们需要的是:
$$H(x) = \sum_{k} \frac{F^k(x)}{k!} = e^{F(x)}$$

虽然我不会多项式$exp$,但是这里的形式比较简单我们可以手推。
我们需要先求出$F(x)$的一个比较简单的形式。根据$f$我们可以写出:
$$F(x) = -\frac{x}{2} + \frac{1}{2} \sum_n \frac{x^n}{n} = -\frac{x}{2} - \frac{1}{2} ln(1 - x)$$

于是有:

$$H(x) = \sum_n h_n \frac{x^n}{(n!)^2} = e^{F(x)} = e^{-\frac{1}{2} x} e^{-\frac{1}{2} ln(1-x)} = e^{-\frac{1}{2}x} (1-x)^{-\frac{1}{2}}$$

前半部分用$e$直接展开:

$$e^{-\frac{1}{2}x} = \sum_n (-\frac{1}{2})^n \frac{x^n}{n!}$$

后半部分用广义二项式展开:

$$(1-x)^{-\frac{1}{2}} = \sum_n \frac{(-\frac{1}{2})^{\underline{n}}}{n!} (-1)^n x^n$$

把这两个卷起来就得到了$H(x)$。

最后我们需要把链和环拼起来,这一步做卷积或手动展开都可以了。

总复杂度是$O(mlogmlog(m-n))$,或者$O(mlogm)$。于是就和$m-n$无关了。

#include <cstdio>
#include <vector>
#include <algorithm> using namespace std; typedef vector<int> Poly; const int N = (int)1e5 + ;
const int MOD = ;
const int _IV2 = (MOD - ) / ; int n, m; namespace {
int fac[N], ifac[N];
int Add(int a, int b) { return (a += b) >= MOD? a - MOD : a; }
int Sub(int a, int b) { return (a -= b) < ? a + MOD : a; }
int Mul(int a, int b) { return (long long)a * b % MOD; }
int Pow(int a, int b) {
int r = ;
for (; b; b >>= , a = Mul(a, a)) if (b & ) r = Mul(r, a);
return r;
}
void Math_Init() {
fac[] = ifac[] = ;
for (int i = ; i < N; ++i) fac[i] = Mul(fac[i - ], i);
ifac[N - ] = Pow(fac[N - ], MOD - );
for (int i = N - ; ~i; --i) ifac[i] = Mul(ifac[i + ], i + );
}
} namespace PO {
int rev[(int)1e6 + ];
void Dft(Poly &a, int le) {
for (int i = ; i < le; ++i)
if (i < rev[i]) swap(a[i], a[rev[i]]);
for (int i = ; i < le; i <<= ) {
int wn = Pow(, (MOD - ) / (i << ));
for (int j = ; j < le; j += i << ) {
int w = , x, y;
for (int k = ; k < i; ++k, w = Mul(w, wn)) {
x = a[j + k];
y = Mul(a[j + k + i], w);
a[j + k] = Add(x, y);
a[j + k + i] = Sub(x, y);
}
}
}
}
void Idft(Poly &a, int le) {
reverse(a.begin() + , a.end());
Dft(a, le);
int iv = Pow(le, MOD - );
for (int i = ; i < le; ++i) a[i] = ::Mul(a[i], iv);
}
Poly Mul(Poly a, Poly b, int lim) {
int n = a.size(), m = b.size(), l;
for (l = ; l < n + m - ; l <<= );
for (int i = ; i < l; ++i)
rev[i] = (rev[i >> ] >> ) | (i & ? l >> : );
a.resize(l), Dft(a, l);
b.resize(l), Dft(b, l);
for (int i = ; i < l; ++i) a[i] = ::Mul(a[i], b[i]);
Idft(a, l), a.resize(lim);
return a;
}
Poly Pow(Poly a, int b, int lim) {
Poly r(lim);
for (r[] = ; b; b >>= ) {
if (b & ) r = Mul(r, a, lim);
a = Mul(a, a, lim);
}
return r;
}
} int main() {
Math_Init();
scanf("%d%d", &n, &m); Poly F0(m + ), F1(m + ), fm(m + );
for (int i = , dw = ; i <= m; ++i) {
F0[i] = Mul(dw, ifac[i]);
if (i & ) F0[i] = Sub(, F0[i]);
dw = Mul(dw, Sub(_IV2, i));
}
for (int i = , pw = ; i <= m; ++i) {
F1[i] = Mul(pw, ifac[i]);
pw = Mul(pw, _IV2);
}
Poly expF = PO::Mul(F0, F1, m + );
for (int i = ; i <= m; ++i) {
fm[i] = Mul(expF[i], Mul(fac[i], fac[i]));
} Poly G(m + ), gk(m + );
G[] = ;
for (int i = ; i <= m; ++i) {
G[i] = (MOD + ) / ;
}
G = PO::Pow(G, m - n, m + );
for (int i = m - n; i <= m; ++i) {
gk[i] = Mul(Mul(G[i], Mul(fac[i], fac[i - m + n])), ifac[m - n]);
} int ans = ;
for (int i = ; i <= n; ++i) {
int ch1 = Mul(fac[m], Mul(ifac[i], ifac[m - i]));
int ch2 = Mul(fac[n], Mul(ifac[i], ifac[n - i]));
ans = Add(ans, Mul(Mul(fm[i], gk[m - i]), Mul(ch1, ch2)));
}
printf("%d\n", ans);
return ;
}

这里简单讲一下为什么有$\sum_{n=1} \frac{x^n}{n} = -ln(1-x)$。

根据定义有:$ln'(x) = \frac{1}{x}$

根据导数的复合:$(ln(1-x))' = ln'(1-x)(1-x)' = \frac{-1}{1-x} = -\sum_n x^n$

对两边积分:$ln(1-x) = -\sum_{n=1} \frac{x^n}{n}$

【LG 4831】Scarlet loves WenHuaKe(生成函数)的更多相关文章

  1. 洛谷P4831 Scarlet loves WenHuaKe

    这道题告诉我们推式子的时候头要够铁. 题意 问一个\(n\times m\)的棋盘,摆上\(n\times 2\)个中国象棋的炮使其两两不能攻击的方案数,对\(998244353\)取模. \((n\ ...

  2. P4831-Scarlet loves WenHuaKe【组合数学】

    正题 题目链接:https://www.luogu.com.cn/problem/P4831 题目大意 \(n*m\)的网格上放置\(2n\)个炮,要求互不能攻击. 数据满足\(n\leq m\leq ...

  3. Luogu P3362 Cool loves shaxian 生成函数

    题意: 定义f(i)=∑ k∣i k^d(i≤n),给出q个询问,每个询问询问区间[l,r]的f(i)的和. n<=1e7 d<=1e18 q<=5e4 可以发现f(i)是个积性函数 ...

  4. ****The Toy of Flandre Scarlet

    The Toy of Flandre Scarlet Time Limit:2000MS     Memory Limit:65536KB     64bit IO Format:%lld & ...

  5. 【loj#6503.】「雅礼集训 2018 Day4」Magic(生成函数+容斥)

    题面 传送门 题解 复杂度比较迷啊-- 以下以\(n\)表示颜色总数,\(m\)表示总的卡牌数 严格\(k\)对比较难算,我们考虑容斥 首先有\(i\)对就代表整个序列被分成了\(m-i\)块互不相同 ...

  6. [BZOJ3456]城市规划(生成函数+多项式求逆+多项式求ln)

    城市规划 时间限制:40s      空间限制:256MB 题目描述 刚刚解决完电力网络的问题, 阿狸又被领导的任务给难住了.  刚才说过, 阿狸的国家有n个城市, 现在国家需要在某些城市对之间建立一 ...

  7. 【题解】歌唱王国(概率生成函数+KMP)+伦讲的求方差

    [题解]歌唱王国(概率生成函数+KMP)+伦讲的求方差 生成函数的本质是什么呀!为什么和It-st一样神 设\(f_i\)表示填了\(i\)个时候停下来的概率,\(g_i\)是填了\(i\)个的时候不 ...

  8. 洛谷 P6295 - 有标号 DAG 计数(生成函数+容斥+NTT)

    洛谷题面传送门 看到图计数的题就条件反射地认为是不可做题并点开了题解--实际上这题以我现在的水平还是有可能能独立解决的( 首先连通这个条件有点棘手,我们尝试把它去掉.考虑这题的套路,我们设 \(f_n ...

  9. FFT/NTT复习笔记&多项式&生成函数学习笔记Ⅰ

    众所周知,tzc 在 2019 年(12 月 31 日)就第一次开始接触多项式相关算法,可到 2021 年(1 月 1 日)才开始写这篇 blog. 感觉自己开了个大坑( 多项式 多项式乘法 好吧这个 ...

随机推荐

  1. 在平衡树的海洋中畅游(四)——FHQ Treap

    Preface 关于那些比较基础的平衡树我想我之前已经介绍的已经挺多了. 但是像Treap,Splay这样的旋转平衡树码亮太大,而像替罪羊树这样的重量平衡树却没有什么实际意义. 然而类似于SBT,AV ...

  2. Luogu P4016 负载平衡问题

    传说中的网络流24题之一,我刷的第二题菜. 据说这种东西做完了就可以有质的飞越?不过看着这些Luogu评级就有点蒙蔽. 首先我们看一下题目发现这不是均分纸牌的加强板吗,但是那个环的操作极大地限制了我的 ...

  3. 你应该学会的接口调试神器——Postman高级用法

    postman这个神器相信大家都用过,程序员作为非专业的测试人员,非常需要这么一款简单轻量级的restful测试工具,但是不知道你是否知道,postman的强大之处不只是测试一下接口,还有其他非常赞的 ...

  4. 20min 快速着手Markdown

    目录 Markdown介绍和基本使用 初步介绍 markdown的使用场景 为什么是 Markdown markdown的基本语法和使用平台 Q&A: Markdown介绍和基本使用 初步介绍 ...

  5. Jenkins日常运维笔记-重启数据覆盖问题、迁移、基于java代码发版(maven构建)

    之前在公司机房部署了一套jenkins环境,现需要迁移至IDC机房服务器上,迁移过程中记录了一些细节:1)jenkins默认的主目录放在当前用户家目录路径下的.jenkins目录中.如jenkins使 ...

  6. part 1

    注意:本次源码分析选择2.0.3(因为不支持IE6.7.8,就少了很多兼容的hack的写法,对了解jQuery的实现原理有很大的帮助) 1.jQuery有不同的版本,从2.x版本便不再支持IE6.7. ...

  7. PHP从入门到精通(六)

    PHP中的错误处理 1.PHP的错误级别:见表格.2.调整PHP错误报告级别:PHP中,调整错误报告级别的方式有两种: ①修改PHP.ini文件的配置项.a.会导致在当前服务器环境下所有PHP文件都受 ...

  8. Linux实践:文件破解

    Linux实践:文件破解 标签(空格分隔): 20135321余佳源 一.掌握NOP.JNE.JE.JMP.CMP汇编指令的机器码 NOP:NOP指令即"空指令".执行到NOP指令 ...

  9. 个人github链接及git学习心得总结

    个人github链接 https://www.github.com/liangjianming/test git学习心得总结​ git是一个快速,开源,分布式的版本控制系统. GitHub是一个基于w ...

  10. git学习笔记——廖雪峰git教程

    OK,先附上教程--廖雪峰的官方网站 友情连接:git官网 简介 这里我只想引用他的原文: Linus可以向BitMover公司道个歉,保证以后严格管教弟兄们,嗯,这是不可能的.实际情况是这样的: L ...