【LG 4831】Scarlet loves WenHuaKe(生成函数)
一道好题,第一次用生成函数做题。感谢赛珂狼教我这个做法。
首先我们显然可以把题目中的限制转化成一个二分图的模型:左边有$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(生成函数)的更多相关文章
- 洛谷P4831 Scarlet loves WenHuaKe
这道题告诉我们推式子的时候头要够铁. 题意 问一个\(n\times m\)的棋盘,摆上\(n\times 2\)个中国象棋的炮使其两两不能攻击的方案数,对\(998244353\)取模. \((n\ ...
- P4831-Scarlet loves WenHuaKe【组合数学】
正题 题目链接:https://www.luogu.com.cn/problem/P4831 题目大意 \(n*m\)的网格上放置\(2n\)个炮,要求互不能攻击. 数据满足\(n\leq m\leq ...
- 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)是个积性函数 ...
- ****The Toy of Flandre Scarlet
The Toy of Flandre Scarlet Time Limit:2000MS Memory Limit:65536KB 64bit IO Format:%lld & ...
- 【loj#6503.】「雅礼集训 2018 Day4」Magic(生成函数+容斥)
题面 传送门 题解 复杂度比较迷啊-- 以下以\(n\)表示颜色总数,\(m\)表示总的卡牌数 严格\(k\)对比较难算,我们考虑容斥 首先有\(i\)对就代表整个序列被分成了\(m-i\)块互不相同 ...
- [BZOJ3456]城市规划(生成函数+多项式求逆+多项式求ln)
城市规划 时间限制:40s 空间限制:256MB 题目描述 刚刚解决完电力网络的问题, 阿狸又被领导的任务给难住了. 刚才说过, 阿狸的国家有n个城市, 现在国家需要在某些城市对之间建立一 ...
- 【题解】歌唱王国(概率生成函数+KMP)+伦讲的求方差
[题解]歌唱王国(概率生成函数+KMP)+伦讲的求方差 生成函数的本质是什么呀!为什么和It-st一样神 设\(f_i\)表示填了\(i\)个时候停下来的概率,\(g_i\)是填了\(i\)个的时候不 ...
- 洛谷 P6295 - 有标号 DAG 计数(生成函数+容斥+NTT)
洛谷题面传送门 看到图计数的题就条件反射地认为是不可做题并点开了题解--实际上这题以我现在的水平还是有可能能独立解决的( 首先连通这个条件有点棘手,我们尝试把它去掉.考虑这题的套路,我们设 \(f_n ...
- FFT/NTT复习笔记&多项式&生成函数学习笔记Ⅰ
众所周知,tzc 在 2019 年(12 月 31 日)就第一次开始接触多项式相关算法,可到 2021 年(1 月 1 日)才开始写这篇 blog. 感觉自己开了个大坑( 多项式 多项式乘法 好吧这个 ...
随机推荐
- item 2: 理解auto类型的推导
本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 如果你已经读过item 1的模板类型推导,你已经知道大部分关于au ...
- Windows环境下实现Consul服务注册和服务发现
1.首先从官方网站下载Consul,因为我们是使用的Windows系统,所以选择windows版本 https://www.consul.io/downloads.html 2.可以用开发者模式来启动 ...
- 【nodejs】让nodejs像后端mvc框架(asp.net mvc)一样处理请求--参数自动映射篇(6/8)
文章目录 前情概要 路由.action的扫描.发现.注册搞定之后,后来我发现在我们的action里面获取参数往往都是通过request对象来一个一个获取.同样的一行代码我们不厌其烦的重复写了无数次.遂 ...
- linux下安装redis组件报错-gcc报错
报错如图: 1.解决办法 先安装gcc插件.删除redis解压后文件.重新解压
- 编写脚本自动部署反向代理、web、nfs
服务器端 #!/bin/bash function nginx_install(){ if [[ -f /usr/sbin/nginx ]]; then echo 'Nginx has been in ...
- mac系统下修复第三方Python包bug
发现问题 今天在github上fork了CI 3.x的中文手册,按照README文档一步步进行Sphinx和相关工具的安装,最终build生成html版手册.操作到第6步执行`make html`的时 ...
- 【实践报告】Linux实践二
3.编译并安装内核与模块 sudo make bzImage –j3 编译内核 sudo make modules –j3 编译模块 sudo make modules ...
- APP推广(预期方案)
首先,在推广过程中有一些定的弊端:我们这个O2O平台暂时只能适用于学校局域网. 因为我们的APP才刚刚“出炉”不久,在网络上还是属于一篇空白的状态,我们想过可以在百度百科上进行相应的推广,如果有用户搜 ...
- java学习--第50天讲到jquery
4月4日jquery讲完了. jquery组合选择器 逗号隔开 层级选择器 父元素 子元素,直接子元素和间接子元素,空格隔开. 直接后代选择器: 父元素>子元素 选择的直接子元素 下 ...
- Selenium vs TestStudio,Selenium Grid vs F2Test
Selenium vs TestStudio,不知道差异在哪里? Selenium Grid vs F2Test,后者更优. 用Selenium + FireFox做了一个单机抓图,想要扩展成集群的话 ...