题目链接

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

首先我们显然可以把题目中的限制转化成一个二分图的模型:左边有$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. MongoDB的导入导出

    一:MongoDB的导出功能 mongodb提供了导入和导出的功能,分别是MongoDB下载目录下的mongoexport.exe和mongoimport.exe文件 ,具体的导出命令格式如下: mo ...

  2. html5录音支持pc和Android、ios部分浏览器,微信也是支持的,JavaScript getUserMedia

    以前在前人基础上重复造了一个网页录音的轮子,顺带把github仓库使用研究了一下,扔到了github上. 优势在于结构简单,可插拔式的录音格式支持,几乎可以支持任意格式(前提有相应的编码器):默认提供 ...

  3. [React]全自动数据表格组件——BodeGrid

    表格是在后台管理系统中用的最频繁的组件之一,相关的功能有数据的新增和编辑.查询.排序.分页.自定义显示以及一些操作按钮.我们逐一深入进行探讨以及介绍我的设计思路:   新增和编辑 想想我们最开始写新增 ...

  4. Linux ip forward

    Linux 默认带有 ip forward 功能,只不过因为各种原因,默认的配置把该功能关闭了.本文通过 demo 来演示 Linux 的 ip forward 功能,具体场景为:开启 Linux 的 ...

  5. 【亲测有效】Kali Linux无法安装网易云音乐的解决方案

    问题描述 由于 Kali Linux 的内核是基于 Debian 的,我们在安装网易云音乐的时候更偏向于选择安装网易云音乐 v1.1.0 deepin15(64位) 的包,可是我发现在安装过程中,无法 ...

  6. Telephone Phrases

    There are some common phrases and sentences you can use when speaking on the telephone. The informal ...

  7. Keepalived基础知识-运维小结

    keepalived介绍keepalived观察其名可知,保持存活,在网络里面就是保持在线了,也就是所谓的高可用或热备,它集群管理中保证集群高可用的一个服务软件,其功能类似于heartbeat,用来防 ...

  8. ACM-ICPC 2018 沈阳赛区网络预赛 G. Spare Tire

    这题很好啊,好在我没做出来...大概分析了一下,题目大概意思就是求 问所有满足1<=i<=n且i与m互素的ai之和 最开始我们队的做法是类似线性筛的方法去筛所有数,把数筛出来后剩下数即可, ...

  9. oracle数据库添加新用户

    /*分为四步 */ /*第1步:创建临时表空间 */ create temporary tablespace kmyf_temp tempfile 'E:\app\pangxy\product\11. ...

  10. [Beta阶段]展示博客

    一.团队成员简介与个人博客地址 团队博客地址:http://www.cnblogs.com/wowotoubuaa/ 江昊,项目经理http://www.cnblogs.com/haoj/ 王开,后端 ...