题目链接

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

首先我们显然可以把题目中的限制转化成一个二分图的模型:左边有$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. [转]curl的错误代码

    转贴者按: 今天在使用curl的时候碰到了一个错误,如下所示: External Program Failed: D:\Tools\curl\curl.exe (return code was 18) ...

  2. 安卓自动化测试,贺晓聪之uiautomator设备和选择器~Python详解

    1.设备对象 引入uiautomator,获取设备对象<所谓设备对象可理解为:Android模拟器或者真机> 语法:from uiautomator import device as d ...

  3. 在平衡树的海洋中畅游(三)——Splay

    Preface 由于我怕学习了Splay之后不直接写blog第二天就忘了,所以强行加了一波优先级. 论谁是天下最秀平衡树,我Splay第一个不服.维护平衡只靠旋转. 一言不合转死你 由于平衡树我也介绍 ...

  4. ORA-12638:Credential retrieval failed(身份证明检索失败)解决方法

    版本:oracle 11g 解决方法: 在sqlnet.ora中设置SQLNET.AUTHENTICATION_SERVICES= 0.本人亲自验证,可以解决此问题. 网上说设置SQLNET.AUTH ...

  5. C/C++中int128的那点事

    最近群友对int128这个东西讨论的热火朝天的.讲道理的话,编译器的gcc是不支持__int128这种数据类型的,比如在codeblocks 16.01/Dev C++是无法编译的,但是提交到大部分O ...

  6. 2019年以后ArcGIS 调用天地图的资源URL

    2019年1月1日起,天地图做出如下变更,导致直接在Arcgis/ArcMap中添加WMTS服务不能用了. 国家天地图解释的很清楚,注册个人用户就可以了. 原有调用方式不变,只要在URL 后添加“&a ...

  7. Node 系列之url模块

    引入 url: const url = require("url"); 用于URL解析.处理等操作的解决方案 1.url.parse(urlStr[, parseQueryStri ...

  8. B. Divisor Subtraction

    链接 [http://codeforces.com/contest/1076/problem/B] 题意 给你一个小于1e10的n,进行下面的运算,n==0 结束,否则n-最小质因子,问你进行多少步 ...

  9. 【2016.3.22】作业 Word count 小程序

    今天更下word count程序的设计思路及实现方法. 我的程序贴在coding里,这里就先不贴出来了, 我的coding地址:https://coding.net/u/holy_angel/p/wo ...

  10. 自己搭建的一个react脚手架

    包括了: react.react router(v4), webpack(v4),echarts, google的组件库material ui, 后期会加上redux但是这些做中小型系统已经够了,de ...