整理的算法模板合集: ACM模板

点我看算法全家桶系列!!!

实际上是一个全新的精炼模板整合计划


繁凡出品的全新系列:解题报告系列 —— 超高质量算法题单,配套我写的超高质量的题解和代码,题目难度不一定按照题号排序,我会在每道题后面加上题目难度指数(1∼51 \sim 51∼5),以模板题难度 111 为基准。


这样大家在学习算法的时候就可以执行这样的流程:

%
阅读【学习笔记】 / 【算法全家桶】学习算法 ⇒\Rightarrow⇒ 阅读相应算法的【解题报告】获得高质量题单 ⇒\Rightarrow⇒ 根据一句话题解的提示尝试自己解决问题 ⇒\Rightarrow⇒ 点开详细题解链接学习巩固(好耶)

%
解题报告系列合集:【解题报告系列】超高质量题单 + 题解(ICPC / CCPC / NOIP / NOI / CF / AT / NC / P / BZOJ)

本题单前置知识: 《小学生都能看懂的生成函数从入门到升天教程》《生成函数全家桶》

目录

  • 零、一些常用公式
  • 一、普通型生成函数
    • A、(P2000) 拯救世界(简单生成函数应用)
    • B、(BZOJ 3771)Triple(普通型生成函数 + FFT)(3)
    • C、(BZOJ3513) [MUTC2013] idiots(普通型生成函数 + FFT + 组合计数)
    • D、(CF438E) The Child and Binary Tree(普通型生成函数 + 多项式求逆 + 多项式开方)
  • 二、指数型生成函数
    • A、(POJ - 3734) Blocks(指数型生成函数)
    • B、(HDU 1521)排列组合(指数型生成函数)
    • C、(2019 上海icpc网络赛 E) Counting Sequences II

零、一些常用公式

普通型生成函数:

单下标序列普通型生成函数

A(x)=∑n=0∞anxnA(x)=\displaystyle\sum_{n=0}^{\infty}a_nx^nA(x)=n=0∑∞​an​xn

双下标序列普通型生成函数

A(x1,x2)=∑n=0∞a(n,m)x1nx2mA(x_1,x_2)=\displaystyle\sum_{n=0}^{\infty}a_{(n,m)}x_1^nx_2^mA(x1​,x2​)=n=0∑∞​a(n,m)​x1n​x2m​

普通型生成函数常用公式

11−x=∑i=0∞xi\frac{1}{1 - x} = \sum\limits_{i = 0}^{\infty} x^i1−x1​=i=0∑∞​xi

ln(1+x)=∑i=0∞(−1)ixi+1i+1ln(1 + x) = \sum\limits_{i = 0}^{\infty} (-1)^{i} \frac{x^{i + 1}}{i + 1}ln(1+x)=i=0∑∞​(−1)ii+1xi+1​

(1+x)a=∑i=0∞ai‾xii!(1 + x)^{a} = \sum\limits_{i = 0}^{\infty} a^{\underline{i}}\frac{x^i}{i!}(1+x)a=i=0∑∞​ai​i!xi​

sin(x)=∑i=0∞(−1)ix2i+1(2i+1)!sin(x) = \sum\limits_{i = 0}^{\infty} (-1)^{i}\frac{x^{2i + 1}}{(2i + 1)!}sin(x)=i=0∑∞​(−1)i(2i+1)!x2i+1​

cos(x)=∑i=0∞(−1)ix2i(2i)!cos(x) = \sum\limits_{i = 0}^{\infty} (-1)^{i}\frac{x^{2i}}{(2i)!}cos(x)=i=0∑∞​(−1)i(2i)!x2i​

指数型生成函数:

指数型生成函数用于解决排列数问题。

一般形式:
G(x)=a0+a1x+a2x22!+a3x33!+a4x44!+⋯=∑i=0∞aixii!G(x) = a_0 + a_1x + a_2\frac{x^2}{2!} + a_3\frac{x^3}{3!} + a_4\frac{x^4}{4!} + \dots = \sum\limits_{i = 0}^{\infty} a_i\frac{x^i}{i!}G(x)=a0​+a1​x+a2​2!x2​+a3​3!x3​+a4​4!x4​+⋯=i=0∑∞​ai​i!xi​
对于两个数列 an{a_n}an​ 和 bn{b_n}bn​ ,对应的指数型生成函数为:
G(x)=∑i=0∞aixii!G(x) = \sum\limits_{i = 0}^{\infty} a_i\frac{x^i}{i!}G(x)=i=0∑∞​ai​i!xi​

F(x)=∑i=0∞bixii!F(x) = \sum\limits_{i = 0}^{\infty} b_i\frac{x^i}{i!}F(x)=i=0∑∞​bi​i!xi​

F(x)×G(x)=(∑i=0∞aixii!)(∑i=0∞bixii!)=∑n=0∞(∑i=0∞aixii!×bn−ixn−i(n−i)!)xn=∑n=0∞(∑i=0∞(ni)aibn−i)xnn!F(x)⋅G(x)\begin{aligned} F(x) \times G(x) &= (\sum\limits_{i = 0}^{\infty} a_i \frac{x^i}{i!})(\sum\limits_{i = 0}^{\infty} b_i \frac{x^i}{i!}) \ &= \sum\limits_{n = 0}^{\infty} (\sum\limits_{i = 0}^{\infty} \frac{a_ix^i}{i!}\times \frac{b_{n - i}x^{n - i}}{(n - i)!})x^n \ &= \sum\limits_{n = 0}^{\infty} (\sum\limits_{i = 0}^{\infty} {n \choose i} a_i b_{n - i}) \frac{x^n}{n!} \end{aligned} F(x)⋅G(x)F(x)×G(x)​=(i=0∑∞​ai​i!xi​)(i=0∑∞​bi​i!xi​) ​=n=0∑∞​(i=0∑∞​i!ai​xi​×(n−i)!bn−i​xn−i​)xn ​=n=0∑∞​(i=0∑∞​(in​)ai​bn−i​)n!xn​​F(x)⋅G(x)

显然 xix^ixi 的系数的实际意义为:选择 iii 个该物品的方案数,则 F(x)×G(x)F(x) \times G(x)F(x)×G(x) 的 xix^ixi 的系数的实际意义即:从 aaa 和 bbb 中选出 iii 个物品的排列数。

指数型生成函数常用公式

ex=∑i=0∞xii!=1+x+x22!+x33!+x44!+⋯+xnn!+…e^{x} = \sum\limits_{i = 0}^{\infty} \frac{x^i}{i!} = 1 + x + \frac{x^2}{2!} + \frac{x^3}{3!} + \frac{x^4}{4!} + \dots + \frac{x^n}{n!} + \dotsex=i=0∑∞​i!xi​=1+x+2!x2​+3!x3​+4!x4​+⋯+n!xn​+…

ex+e−x2=∑i=0∞x2i(2i)!\frac{e^x + e^{-x}}{2} = \sum\limits_{i = 0}^{\infty} \frac{x^{2i}}{(2i)!}2ex+e−x​=i=0∑∞​(2i)!x2i​

ex−e−x2=∑i=0∞x2i+1(2i+1)!\frac{e^x - e^{-x}}{2} = \sum\limits_{i = 0}^{\infty} \frac{x^{2i + 1}}{(2i + 1)!}2ex−e−x​=i=0∑∞​(2i+1)!x2i+1​
二项式定理

(x+1)n=∑i=0nCnixi(x+1)^n=\sum_{i=0}^{n} C_n^i~ x^i (x+1)n=i=0∑n​Cni​ xi

(1+x)−n=11+x=∑i=0∞C−nixi=∑i=0∞(−1)iCn+i−1ixi\begin{aligned}(1+x)^{-n}&=\cfrac{1}{1+x}\\ &=\sum_{i=0}^{\infty} C_{-n}^{\ i} ~ x^i\\&=\sum_{i=0}^{\infty} (-1)^i~C_{n+i-1}^{\ i}~x^i\end{aligned} (1+x)−n​=1+x1​=i=0∑∞​C−n i​ xi=i=0∑∞​(−1)i Cn+i−1 i​ xi​

(1−x)n=∑i=0∞(−1)iCnixi(1−x)n=∑i=0∞(−1)iCnixi(1-x)^n=\sum_{i=0}^{\infty} (-1)^i~ C_n^ {~i}~x^i (1-x)^n=\sum_{i=0}^{\infty} (-1)^i~ C_n^{~i}~x^i (1−x)n=i=0∑∞​(−1)i Cn i​ xi(1−x)n=i=0∑∞​(−1)i Cn i​ xi

(x+y)n=∑i=0nCniai⋅bn−i(x+y)^n=\sum_{i=0}^{n} C_n^{\ i} a^i·b^{n-i} (x+y)n=i=0∑n​Cn i​ai⋅bn−i

1(1−x)n=∑i=0∞Cn+i−1ixi\cfrac{1}{(1-x)^{n}} =\sum_{i=0}^{\infty} C_{n+i-1}^{\ i}~x^i(1−x)n1​=i=0∑∞​Cn+i−1 i​ xi

二项式定理学习笔记(详解)

一、普通型生成函数

A、(P2000) 拯救世界(简单生成函数应用)

显然考虑普通型生成函数。

我们只需要按照题意把他们的生成函数列出来,然后转换成统一的形式相乘即可:

金神石的块数必须是6的倍数:1+x6+x12+⋯=11−x61+x^6+x^{12}+\dots=\cfrac{1}{1-x^6}1+x6+x12+⋯=1−x61​

S=1+x6+x12+…S=1+x^6+x^{12}+\dotsS=1+x6+x12+…

x6S=x6+x12+…x^6S=x^6+x^{12}+\dotsx6S=x6+x12+…

S−x6S=1S-x^6S=1S−x6S=1
S=11−x6S=\cfrac{1}{1-x^6}S=1−x61​

木神石最多用9块:1+x+x2+⋯+x9=1−x101−x1+x+x^2+\dots+x^9=\cfrac{1-x^{10}}{1-x}1+x+x2+⋯+x9=1−x1−x10​

水神石最多用5块:1+x+x2+⋯+x5=1−x61−x1+x+x^2+\dots+x^5=\cfrac{1-x^6}{1-x}1+x+x2+⋯+x5=1−x1−x6​

火神石的块数必须是4的倍数:1+x4+x8+⋯=11−x41+x^4+x^8+\dots=\cfrac{1}{1-x^4}1+x4+x8+⋯=1−x41​

土神石最多用7块:1+x+x2+⋯+x7=1−x81−x1+x+x^2+\dots+x^7=\cfrac{1-x^8}{1-x}1+x+x2+⋯+x7=1−x1−x8​

金神石的块数必须是2的倍数:1+x2+x4+⋯=11−x21+x^2+x^4+\dots=\cfrac{1}{1-x^2}1+x2+x4+⋯=1−x21​

木神石最多用1块:1+x=1−x21−x1+x=\cfrac{1-x^2}{1-x}1+x=1−x1−x2​

水神石的块数必须是8的倍数:1+x8+x16+⋯=11−x81+x^8+x^{16}+\dots=\cfrac{1}{1-x^8}1+x8+x16+⋯=1−x81​

火神石的块数必须是10的倍数:1+x10+x20+⋯=11−x101+x^{10}+x^{20}+\dots=\cfrac{1}{1-x^{10}}1+x10+x20+⋯=1−x101​

土神石最多用3块:1+x+x2+x3=1−x41−x1+x+x^2+x^3=\cfrac{1-x^4}{1-x}1+x+x2+x3=1−x1−x4​

答案显然就是将他们卷起来之后 xnx^nxn 的系数。

11−x6⋅1−x101−x⋅1−x61−x⋅11−x4⋅1−x81−x⋅11−x2⋅1−x21−x⋅11−x8⋅11−x10⋅1−x41−x=(11−x)5\frac{1}{1-x^6}\cdot \frac{1-x^{10}}{1-x} \cdot \frac{1-x^6}{1-x} \cdot \frac{1}{1-x^4} \cdot \frac{1-x^8}{1-x} \cdot \frac{1}{1-x^2} \cdot \frac{1-x^2}{1-x} \cdot \frac{1}{1-x^8} \cdot \frac{1}{1-x^{10}} \cdot \frac{1-x^4}{1-x}\\ =(\frac{1}{1-x})^51−x61​⋅1−x1−x10​⋅1−x1−x6​⋅1−x41​⋅1−x1−x8​⋅1−x21​⋅1−x1−x2​⋅1−x81​⋅1−x101​⋅1−x1−x4​=(1−x1​)5

根据广义二项式定理:
1(1−x)n=∑i=0∞Cn+i−1ixi\cfrac{1}{(1-x)^{n}} =\sum_{i=0}^{\infty} C_{n+i-1}^{\ i}~x^i (1−x)n1​=i=0∑∞​Cn+i−1 i​ xi

答案就是 xnx^nxn 项的系数,即 i=5i=5i=5

ans=Cn+4n=(n+4)(n+3)(n+2)(n+1)4!(=24)ans = C_{n+4}^{\ n}=\cfrac{(n+4)(n+3)(n+2)(n+1)}{4!(=24)}ans=Cn+4 n​=4!(=24)(n+4)(n+3)(n+2)(n+1)​

B、(BZOJ 3771)Triple(普通型生成函数 + FFT)(3)

Link

https://darkbzoj.tk/problem/3771

Problem

我们讲一个悲伤的故事。
从前有一个贫穷的樵夫在河边砍柴。
这时候河里出现了一个水神,夺过了他的斧头,说:
“这把斧头,是不是你的?”
樵夫一看:“是啊是啊!”
水神把斧头扔在一边,又拿起一个东西问:
“这把斧头,是不是你的?”
樵夫看不清楚,但又怕真的是自己的斧头,只好又答:“是啊是啊!”
水神又把手上的东西扔在一边,拿起第三个东西问:
“这把斧头,是不是你的?”
樵夫还是看不清楚,但是他觉得再这样下去他就没法砍柴了。
于是他又一次答:“是啊是啊!真的是!”
水神看着他,哈哈大笑道:
“你看看你现在的样子,真是丑陋!”
之后就消失了。
樵夫觉得很坑爹,他今天不仅没有砍到柴,还丢了一把斧头给那个水神。
于是他准备回家换一把斧头。
回家之后他才发现真正坑爹的事情才刚开始。
水神拿着的的确是他的斧头。
但是不一定是他拿出去的那把,还有可能是水神不知道怎么偷偷从他家里拿走的。
换句话说,水神可能拿走了他的一把,两把或者三把斧头。
樵夫觉得今天真是倒霉透了,但不管怎么样日子还得过。
他想统计他的损失。
樵夫的每一把斧头都有一个价值,不同斧头的价值不同。总损失就是丢掉的斧头价值和。
他想对于每个可能的总损失,计算有几种可能的方案。
注意:如果水神拿走了两把斧头a和b,(a,b)和(b,a)视为一种方案。拿走三把斧头时,(a,b,c),(b,c,a),(c,a,b),(c,b,a),(b,a,c),(a,c,b)视为一种方案。

Input

第一行是整数N,表示有N把斧头。
接下来n行升序输入N个数字Ai,表示每把斧头的价值。

Output

若干行,按升序对于所有可能的总损失输出一行x y,x为损失值,y为方案数。

Solution

题意可以简化为:

给定 nnn 个互不相同的数,问你从这 nnn 个数中选出 1,2,31,2,31,2,3个(不能重复选),所选的三个数的和的方案数是多少,输出所有可能的和 xxx 以及 xxx 的方案数 yyy 。

注:若选取了两个数 aaa 和 bbb ,(a,b)(a,b)(a,b) 和 (b,a)(b,a)(b,a) 视为一种方案。若选取了三个数 aaa 、 bbb 和 ccc,(a,b,c),(b,c,a),(c,a,b),(c,b,a),(b,a,c),(a,c,b)(a,b,c),(b,c,a),(c,a,b),(c,b,a),(b,a,c),(a,c,b)(a,b,c),(b,c,a),(c,a,b),(c,b,a),(b,a,c),(a,c,b)视为一种方案。

这是一个比较基础的生成函数的应用,显然这里求的是组合数,所以我们选择普通型生成函数

nnn 个数只能选一次,有这个限制条件不好直接计算,正难则反,我们可以先忽略这个限制条件,也就是先考虑每个数可以选择任意次的方案数,然后利用容斥原理(奇加偶减)减去重复选的方案数即可。

我们这里使用生成函数,因为这里的价值是每个数的权值而不是个数什么的,所以我们设若选择了一个权值为 www 的物品,则 xwx^wxw 的系数 a[w] ++

这样我们就可以设 A(x)A(x)A(x) 表示每个物品选择一次的生成函数, B(x)B(x)B(x) 表示每个物品选择两次的生成函数, C(x)C(x)C(x) 表示每个物品选择三次的生成函数。

  • 先考虑选择三个物品的情况

考虑去掉重复。

选择了三个物品,首先考虑有同一个物品选择了两次的冗余情况,即三个物品中有两个是重复选择的,也就是 C32C_{3}^{2}C32​,两个重复选择的方案数为 B(x)B(x)B(x),乘上剩下一个物品 A(x)A(x)A(x),此时答案减去 C32×B(x)×A(x)=3B(x)A(x)C_3^2\times B(x)\times A(x)=3B(x)A(x)C32​×B(x)×A(x)=3B(x)A(x)。

然后考虑有同一个物品选择了三次的冗余情况。

很明显同一个物品选择了三次的方案数为 C(x)C(x)C(x),但是我们在同一个物品选择了两次的时候已经减去过了,因为两个相同,加上另一个,另一个也有可能与这两个相同。并且因为上面减去的时候减掉了三倍,也就是多减了两倍,所以再加上二倍,即答案再加上2×C(x)2\times C(x)2×C(x)。(奇加偶减)

最后因为题目中说了选择三个物品的时候有 666 种是重复的,所以需要除以 666。

即选择三个物品的方案数为:
ans=A(x)3−3B(x)A(x)+2C(x)6ans=\frac{A(x)^3-3B(x)A(x)+2C(x)}{6} ans=6A(x)3−3B(x)A(x)+2C(x)​

  • 再考虑选择两个物品的情况

显然不考虑重复选择两个物品的方案数为 A(x)2A(x)^2A(x)2,去掉冗余,即减去 B(x)B(x)B(x)。因为 B(x)B(x)B(x) 的含义就是一个物品被选择了两次。

并且题目种告诉我们了选择两个数有两种是重复的,所以除以 222。

即选择两个物品的方案数为:

ans=A(x)2−B(x)2ans= \frac{A(x)^2-B(x)}{2} ans=2A(x)2−B(x)​

  • 最后考虑选择一个物品的情况

显然此时方案数为 A(x)A(x)A(x)

故总方案数为:

ans=A(x)3−3B(x)A(x)+2C(x)6+A(x)2−B(x)2+A(x)ans=\frac{A(x)^3-3B(x)A(x)+2C(x)}{6}+\frac{A(x)^2-B(x)}{2}+A(x) ans=6A(x)3−3B(x)A(x)+2C(x)​+2A(x)2−B(x)​+A(x)

直接FFT求卷积即可。

Code

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <map>
#include <queue>
using namespace std;
typedef long long ll;
typedef int itn;
typedef pair<int, int>PII;
const int N = 2e5 + 7, mod = 1e9 + 7;
const double PI = acos(-1.0);int n, m, limit, L, RR[N], maxx;struct Complex
{double x, y;Complex(double x = 0, double y = 0) : x(x), y(y) { }
}a[N], A[N], B[N], C[N], D[N], ans[N];Complex operator + (Complex a, Complex b) {return Complex(a.x + b.x, a.y + b.y);}
Complex operator - (Complex a, Complex b) {return Complex(a.x - b.x, a.y - b.y);}
Complex operator * (Complex a, Complex b) {return Complex(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x);}void FFT(Complex *A, int type)
{for(int i = 0; i < limit; ++ i)if(i < RR[i])swap(A[i], A[RR[i]]);for(int mid = 1; mid < limit; mid <<= 1) {Complex wn(cos(PI / mid), (double)type * sin(PI / mid));for(int len = mid << 1, pos = 0; pos < limit; pos += len) {Complex w(1, 0);for(int k = 0; k < mid; ++ k, w = w * wn) {Complex x = A[pos + k];Complex y = w * A[pos + k + mid];A[pos + k] = x + y;A[pos + mid + k] = x - y;}}}if(type == -1) {for(int i = 0; i < limit; ++ i) {A[i].x /= limit;}}
}void solve()
{Complex w3(3.0, 0);Complex w2(2.0, 0), w1in6(1.0 / 6.0, 0), w1in2(1.0 / 2.0, 0);scanf("%d", &n);for(int i = 1; i <= n; ++ i) {int x;scanf("%d", &x);A[x].x ++ , B[x * 2].x ++ , C[x * 3].x ++ ;maxx = max(maxx, x * 3);}limit = 1, L = 0;while(limit <= maxx) limit <<= 1, L ++ ;for(int i = 0; i < limit; ++ i) {RR[i] = (RR[i >> 1] >> 1) | ((i & 1) << (L - 1));}FFT(A, 1), FFT(B, 1), FFT(C, 1);for(int i = 0; i <= limit; ++ i) {ans[i] = ans[i] + ((A[i] * A[i] * A[i] - w3 * B[i] * A[i] + w2 * C[i]) * w1in6);ans[i] = ans[i] + (A[i] * A[i] - B[i]) * w1in2;ans[i] = ans[i] + A[i];}FFT(ans, -1);for(int i = 0; i < limit; ++ i) {int res = (int)(ans[i].x + 0.5);if(res)printf("%d %d\n", i, res);}
}int main()
{solve();
}

C、(BZOJ3513) [MUTC2013] idiots(普通型生成函数 + FFT + 组合计数)

Weblink

https://darkbzoj.tk/problem/3513

Problem

给定 nnn 个长度分别为 aia_iai​ 的木棒,问随机选择 333 个木棒能够拼成三角形的概率。

input

第一行T( T≤100T\le 100T≤100 ),表示数据组数。
接下来若干行描述 T 组数据,每组数据第一行是 n ,接下来一行有 n 个数表示 aia_iai​。

3≤N≤105,1≤ai≤1053≤N≤10^5,1≤a_i≤10^53≤N≤105,1≤ai​≤105

output

T 行,每行一个整数,四舍五入保留7位小数。

Solution

首先我们知道作为一个三角形,两边之和大于第三边。

答案要求的概率很明显就是能组成的三角形的方案数除以总方案数。

如果我们直接去统计一共有多少个符合要求的方案数的话无从下手(好吧,正着做好像也没什么区别),考虑经典正难则反。

我们首先计算总方案数,即从 nnn 个木棒中选择三个,即

tot=Cn3=n×(n−1)×(n−2)/3/2tot=C_{n}^{3}=n\times (n-1)\times (n-2) / 3 / 2tot=Cn3​=n×(n−1)×(n−2)/3/2。

那么我们来尝试统计一下不符合要求的三个木棒的方案数。

不符合要求也就意味着是两个木棒 a,ba,ba,b ,以及一个木棒 ccc ,c≥a+bc\ge a+bc≥a+b,这样就不能组成一个三角形。

因为是统计方案数,且数据较大(10510^5105)不能暴力,只能 O(nlogn)O(nlogn)O(nlogn) 来做,所以经典的 生成函数 + FFT。

所以我们考虑组合计数的思路:

即利用乘法原理,我们只需要求出左半部分 AAA 的个数以及相对应的右半部分 BBB 的个数,相乘并累加即可。

也就是:

我们用 t[i]t[i]t[i] 统计所有大于等于 iii 的木棒的个数。

g[i]g[i]g[i] 表示所有两个木棒的和为 iii 的木棒的个数,那么所有不符合要求的方案数也就是两个木棒 a,ba,ba,b 以及一个 c≥a+bc\ge a+bc≥a+b 的方案数显然就是 illegal=∑i=0maxxg[i]×t[i]\displaystyle illegal=\sum_{i = 0}^{maxx}g[i] \times t[i]illegal=i=0∑maxx​g[i]×t[i] 。

最终的概率显然就是 tot−illegaltot\cfrac{tot-illegal}{tot}tottot−illegal​

那么也就意味着我们只需要计算 g[i]g[i]g[i] 就可以得到答案。

我们考虑如何求得 g[i]g[i]g[i]。

g[i]g[i]g[i] 的实际意义就是 a+b=ia+b=ia+b=i 的方案数,可以理解为选择两个物品 a,ba,ba,b,权值和为 iii 的方案数,显然我们可以用生成函数求解。

我们设 f[i]f[i]f[i] 表示长度为 iii 的木棒数

显然 g[i]=∑f[j]×f[i−j]g[i] = \sum f[j]\times f[i - j]g[i]=∑f[j]×f[i−j]。

一个标准的卷积形式可以直接用 FFT 求解。

但是很明显我们将两个 fff 卷起来会有重复,因为我们只有一个 fff 序列,自己凑,但是生成函数在卷的时候是当成两个相同的 fff 序列卷起来的,例如 1+9=101+9=101+9=10,两个 fff ,所以会有两个 111 和两个 999 ,但实际上只有一个 111 和 1个 999 ,方案数应该是 [1,9],[9,1][1,9],[9,1][1,9],[9,1] 两种,但是生成函数 fff 卷的时候会得到 [1,9],[9,1],[1,9],[9,1][1,9],[9,1],[1,9],[9,1][1,9],[9,1],[1,9],[9,1] 四种方案数,所以最后的方案数要除以 222。并且注意对于序列中仅有一个 555 ,卷的时候会出现 5+5=105+5=105+5=10 这种本来是没有的方案数,如果我们除以二的话也不一定能将其消掉(奇数个除以二下取整会消掉,但万一有偶数个这样的呢)所以我们可以在输入的时候,每次输入 xxx ,就将 g[2×x]−1g[2\times x]-1g[2×x]−1 ,这样就可以将本身不合法的自己加自己(5+5=105+5=105+5=10) 方案数减去。正因如此,我们需要先将 fff 与 ggg 加起来消掉自己加自己之后再除以二消掉冗余的双倍方案。

AC Code

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <map>
#include <queue>
using namespace std;
typedef long long ll;
typedef int itn;
#define int long long
typedef pair<int, int>PII;
const int N = 5e5 + 7, mod = 1e6;
const double PI = acos(-1.0);int n, m, k, L, limit = 1;
int g[N], t[N], RR[N];struct Complex
{double x, y;Complex (double x = 0, double y = 0) : x(x), y(y) { }
}f[N];Complex operator + (Complex a, Complex b) {return Complex (a.x + b.x, a.y + b.y);}
Complex operator - (Complex a, Complex b) {return Complex (a.x - b.x, a.y - b.y);}
Complex operator * (Complex a, Complex b) {return Complex (a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x);}void FFT(Complex *A, double type)
{for(int i = 0; i < limit; ++ i) {if(i < RR[i])swap(A[i], A[RR[i]]);}for(int mid = 1; mid < limit; mid <<= 1) {Complex wn(cos(PI / mid), type * sin(PI / mid));for(int len = mid << 1, pos = 0; pos < limit; pos += len) {Complex w(1, 0);for(int k = 0; k < mid; ++ k, w = w * wn) {Complex x = A[pos + k];Complex y = w * A[pos + mid + k];A[pos + k] = x + y;A[pos + mid + k] = x - y;}}}if(type == -1) {for(int i = 0; i <= limit; ++ i)A[i].x /= limit;}
}void init()
{memset(g, 0, sizeof g);memset(f, 0, sizeof f);memset(t, 0, sizeof t);}void solve()
{init();scanf("%lld", &n);int maxx = 0;ll tot = (n * (n - 1) * (n - 2)) / 6;for(int i = 1; i <= n; ++ i) {ll x;scanf("%lld", &x);g[x << 1] -- ;t[x] ++ ;f[x].x ++ ;maxx = max(maxx, x);}for(int i = maxx - 1; i >= 1; -- i)t[i] += t[i + 1];//大于等于 i 的木棒数量maxx = maxx - 1 << 1;L = 0, limit = 1;while(limit <= maxx) limit <<= 1, L ++ ;for(int i = 0; i <= limit; ++ i) {RR[i] = (RR[i >> 1] >> 1) | ((i & 1) << (L - 1));}FFT(f, 1);for(int i = 0; i < limit; ++ i) {f[i] = f[i] * f[i];}FFT(f, -1);for(int i = 0; i < limit; ++ i) {g[i] += (int)(f[i].x + 0.5);}for(int i = 0; i < limit; ++ i)g[i] >>= 1;ll illegal = 0;for(int i = 0; i < limit; ++ i) {illegal += g[i] * t[i];}double res = (double)(tot - illegal) / tot;printf("%.7f\n", res);return ;
}signed main()
{itn t;scanf("%lld", &t);while(t -- ) {solve();}return 0;
}

D、(CF438E) The Child and Binary Tree(普通型生成函数 + 多项式求逆 + 多项式开方)


首先我们发现模数为998244353998244353998244353,很自然的想到NTT!!!想到用多项式来求解本题。

具有同样效果的数字还有:1004535809、469762049…1004535809、469762049 \dots1004535809、469762049…

显然: 998244353→NTT998244353\to NTT998244353→NTT(bushi)题目中要求的是权值和为 sss 方案数,即点集中选取若干个点组成一颗权值和为 sss 的树的方案数,经典生成函数。我们设序列 fif_ifi​ 表示权值和为 iii 的符合条件的二叉树的个数,gig_igi​​ 表示权值 iii 是否包含在 ccc 中,显然有 f0=1f_0=1f0​=1,根据题意可得:
fn=∑i=1ngi∑j=1n−ifjfn−i−j[n>0]f_n=\sum_{i=1}^ng_i\sum_{j=1}^{n-i}f_jf_{n-i-j}[n>0]fn​=i=1∑n​gi​j=1∑n−i​fj​fn−i−j​[n>0]

经典操作,输入的时候处理一下 gig_igi​ ,经典动规及乘法原理:权值和为 nnn 的方案数 === 权值和为 jjj 的方案数 ×\times× 权值和为 n−i−jn-i-jn−i−j 的方案数 ×gi(i是否存在)\times\ g_i\ (\text{i是否存在})× gi​ (i是否存在) 我们只需要令 FFF 表示序列 fff 的生成函数,GGG 表示序列 ggg 的生成函数。 则:F=G×F2+1F=G\times F^2+1F=G×F2+1。生成函数 GGG 已经预处理了,求出 FFF 即可。问题变成了一个一元二次方程,显然可以直接使用求根公式:F=1±1−4G2GF=\cfrac{1± \sqrt{1-4G}}{2G}F=2G1±1−4G​​。 因为 2G2G2G 可能为 000 不可求逆,所以需要化简移项: F=21±1−4GF=\cfrac{2}{1±\sqrt{1-4G}}F=1±1−4G​2​。因为 F0=1,G0=0F_0=1,G_0=0F0​=1,G0​=0,代入发现当且仅当符号取 +++ 时初始值成立, 故取 +++ ,即:F=21+1−4GF=\cfrac{2}{1+\sqrt{1-4G}}F=1+1−4G​2​

我们直接多项式开根 + 多项式求逆即可。

//#pragma GCC optimize(2)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;const int N = 1000007;
const int p = 998244353, gg = 3, ig = 332738118, img = 86583718;
const int mod = 998244353;template <typename T>void read(T &x)
{x = 0;register int f = 1;register char ch = getchar();while(ch < '0' || ch > '9') {if(ch == '-')f = -1;ch = getchar();}while(ch >= '0' && ch <= '9') {x = x * 10 + ch - '0';ch = getchar();}x *= f;
}int qpow(int a, int b)
{int res = 1;while(b) {if(b & 1) res = 1ll * res * a % mod;a = 1ll * a * a % mod;b >>= 1;}return res;
}namespace Poly
{#define mul(x, y) (1ll * x * y >= mod ? 1ll * x * y % mod : 1ll * x * y)#define minus(x, y) (1ll * x - y < 0 ? 1ll * x - y + mod : 1ll * x - y)#define plus(x, y) (1ll * x + y >= mod ? 1ll * x + y - mod : 1ll * x + y)#define ck(x) (x >= mod ? x - mod : x)//取模运算太慢了typedef vector<int> poly;const int G = 5;const int inv_G = qpow(G, mod - 2);int RR[N], deer[2][19][N], inv[N];void init(const int t) {//预处理出来NTT里需要的w和wn,砍掉了一个log的时间for(int p = 1; p <= t; ++ p) {int buf1 = qpow(G, (mod - 1) / (1 << p));int buf0 = qpow(inv_G, (mod - 1) / (1 << p));deer[0][p][0] = deer[1][p][0] = 1;for(int i = 1; i < (1 << p); ++ i) {deer[0][p][i] = 1ll * deer[0][p][i - 1] * buf0 % mod;//逆deer[1][p][i] = 1ll * deer[1][p][i - 1] * buf1 % mod;}}inv[1] = 1;for(int i = 2; i <= (1 << t); ++ i)inv[i] = 1ll * inv[mod % i] * (mod - mod / i) % mod;}int NTT_init(int n) {//快速数论变换预处理int limit = 1, L = 0;while(limit < n) limit <<= 1, L ++ ;for(int i = 0; i < limit; ++ i)RR[i] = (RR[i >> 1] >> 1) | ((i & 1) << (L - 1));return limit;}void NTT(poly &A, int type, int limit) {//快速数论变换A.resize(limit);for(int i = 0; i < limit; ++ i)if(i < RR[i])swap(A[i], A[RR[i]]);for(int mid = 2, j = 1; mid <= limit; mid <<= 1, ++ j) {int len = mid >> 1;for(int pos = 0; pos < limit; pos += mid) {int *wn = deer[type][j];for(int i = pos; i < pos + len; ++ i, ++ wn) {int tmp = 1ll * (*wn) * A[i + len] % mod;A[i + len] = ck(A[i] - tmp + mod);A[i] = ck(A[i] + tmp);}}}if(type == 0) {for(int i = 0; i < limit; ++ i)A[i] = 1ll * A[i] * inv[limit] % mod;}}poly poly_mul(poly A, poly B) {//多项式乘法int deg = A.size() + B.size() - 1;int limit = NTT_init(deg);poly C(limit);NTT(A, 1, limit);NTT(B, 1, limit);for(int i = 0; i < limit; ++ i)C[i] = 1ll * A[i] * B[i] % mod;NTT(C, 0, limit);C.resize(deg);return C;}poly poly_inv(poly &f, int deg) {//多项式求逆if(deg == 1)return poly(1, qpow(f[0], mod - 2));poly A(f.begin(), f.begin() + deg);poly B = poly_inv(f, (deg + 1) >> 1);int limit = NTT_init(deg << 1);NTT(A, 1, limit), NTT(B, 1, limit);for(int i = 0; i < limit; ++ i)A[i] = B[i] * (2 - 1ll * A[i] * B[i] % mod + mod) % mod;NTT(A, 0, limit);A.resize(deg);return A;}poly poly_sqrt(poly &f, int deg) {//多项式开方if(deg == 1) return poly(1, 1);poly A(f.begin(), f.begin() + deg);poly B = poly_sqrt(f, (deg + 1) >> 1);poly IB = poly_inv(B, deg);int limit = NTT_init(deg << 1);NTT(A, 1, limit), NTT(IB, 1, limit);for(int i = 0; i < limit; ++ i)A[i] = 1ll * A[i] * IB[i] % mod;NTT(A, 0, limit);for(int i =0; i < deg; ++ i)A[i] = 1ll * (A[i] + B[i]) * inv[2] % mod;A.resize(deg);return A;}
}using Poly::poly;
using Poly::poly_sqrt;
using Poly::poly_inv;int n, m, x, k, type;char s[N];
int cnt;int main()
{Poly::init(18);//2^21 = 2,097,152,根据题目数据多项式项数的大小自由调整,注意大小需要跟deer数组同步(21+1=22)read(n), read(m);poly f(N), g(N), tmp(N);for(int i = 0; i < n; ++ i)read(x), g[x] = 1;tmp[0] = 1;for(int i = 1; i <= m; ++ i) tmp[i] = mod - 4 * g[i] % mod;g = poly_sqrt(tmp, m + 1);++ g[0];f = poly_inv(g, m + 1);for(int i = 1; i <= m; ++ i) f[i] = f[i] * 2 % mod;for(int i = 1; i <= m; ++ i) printf("%d\n", f[i]);return 0;
}

二、指数型生成函数

A、(POJ - 3734) Blocks(指数型生成函数)

Weblink

https://vjudge.net/problem/POJ-3734

Problem && Solution

Code

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <set>
using namespace std;
const int mod = 10007;
int n, m;int qpow(int a, int b)
{b %= mod - 1;int res = 1;while(b) {if(b & 1) res = 1ll * res * a % mod;a = 1ll * a * a % mod;b >>= 1;}return res;
}int main()
{int t;scanf("%d", &t);while(t -- ) {scanf("%d", &n);printf("%lld\n", (1ll * qpow(4, n - 1) + qpow(2, n - 1)) % mod);}return 0;
}

B、(HDU 1521)排列组合(指数型生成函数)

Weblink

http://acm.hdu.edu.cn/showproblem.php?pid=1521

Problem

有n种物品,并且知道每种物品的数量。要求从中选出m件物品的排列数。例如有两种物品A,B,并且数量都是1,从中选2件物品,则排列有"AB","BA"两种。n,m≤10n,m\le 10n,m≤10 。

Solution

显然直接生成函数:

(1+x+x22!+…+xa1a1!)∗(1+x+x22!+…+xa2a2!)∗..∗(1+x+x22!+…+xanan!)\left(1+x+\frac{x^{2}}{2 !}+\ldots+\frac{x^{a_1}}{a_{1} !}\right) *\left(1+x+\frac{x^{2}}{2 !}+\ldots+\frac{x^{a_2}}{a_{2} !}\right) * . . *\left(1+x+\frac{x^{2}}{2 !}+\ldots+\frac{x^{a_{n}}}{a_{n} !}\right) (1+x+2!x2​+…+a1​!xa1​​)∗(1+x+2!x2​+…+a2​!xa2​​)∗..∗(1+x+2!x2​+…+an​!xan​​)

数据较小,直接暴力计算即可。

C、(2019 上海icpc网络赛 E) Counting Sequences II

2019 ACM - ICPC 上海网络赛 E. Counting Sequences II (指数型生成函数)

Weblink

https://nanti.jisuanke.com/t/41413

Problem

请你构造一个序列 a1,a2⋯ana_1,a_2\cdots a_na1​,a2​⋯an​,满足 1≤a[i]≤m1\le a[i]\le m1≤a[i]≤m ,其中满足 偶数 出现的次数是 偶数,问构造这样一个序列的方案数。其中 n≤105,m≤2×105n\le 10^5, m\le 2 \times 10^5n≤105,m≤2×105 。

Solution

显然求的是排列数,考虑指数型生成函数:

数据只有 2×1052\times 10^52×105,直接暴力计算即可。

Code

#include <bits/stdc++.h>
using namespace std;const int N = 5e5 + 7, mod = 1000000007;
#define int long long
#define mult(x, y) (1ll * x * y >= mod ? 1ll * x * y % mod : 1ll * x * y)
#define minus(x, y) (1ll * x - y < 0 ? 1ll * x - y + mod : 1ll * x - y)
#define plus(x, y) (1ll * x + y >= mod ? 1ll * x + y - mod : 1ll * x + y)
#define ck(x) (x >= mod : x - mod : x)
typedef long long ll;int n, m, t;
ll infact[N], fact[N];ll qpow(ll a, ll b)
{b %= mod - 1;ll res = 1;while(b) {if(b & 1) res = res * a % mod;a = a * a % mod;b >>= 1;}return res;
}ll inv(int x)
{return qpow(x, mod - 2);
}void init(int n)
{fact[0] = infact[0] = 1;for(ll i = 1; i <= n; ++ i) {fact[i] = (fact[i - 1] * i) % mod;infact[i] = (infact[i - 1] * inv(i)) % mod;}
}ll C(int n, int m)
{if(n < m) return 0;return fact[n] * infact[n - m] % mod * infact[m] % mod;
}void solve()
{scanf("%lld%lld", &n, &m);if(m & 1) {ll b = m / 2;ll pow2 = qpow(2, b);ll inv2 = inv(pow2);ll ans = 0;for(int i = 0; i <= b; ++ i) {ans = (ans + C(b, i) * qpow(m - 2 * i, n) % mod) % mod;}ans = ans * inv2 % mod;printf("%lld\n", ans);}else {ll b = m / 2;ll pow2 = qpow(2, b);ll inv2 = inv(pow2);ll ans = 0;for(int i = 0; i <= b; ++ i) {ans = (ans + C(b, i) * qpow(2 * i, n) % mod) % mod;}ans = ans * inv2 % mod;printf("%lld\n", ans);}
}signed main()
{init(N - 7);scanf("%lld", &t);while(t -- ) {solve();}return 0;
}

解题报告(四)生成函数(ACM/ OI)相关推荐

  1. [精品]CSAPP Bomb Lab 解题报告(四)

    接上篇[精品]CSAPP Bomb Lab 解题报告(三) gdb常用指令 设置Intel代码格式:set disassembly-flavor intel 查看反汇编代码:disas phase_1 ...

  2. 2014 ACM/ICPC 北京赛区网络赛解题报告汇总

    首页 算法竞赛» 信息聚合 ONLINE JUDGE 书刊杂志 BLOG» 新闻故事» 招聘信息» 投稿须知 2014 ACM/ICPC 北京赛区网络赛解题报告汇总 九月 21st, 2014 | P ...

  3. [精品]CSAPP Bomb Lab 解题报告(五)

    接上篇[精品]CSAPP Bomb Lab 解题报告(四) gdb常用指令 设置Intel代码格式:set disassembly-flavor intel 查看反汇编代码:disas phase_1 ...

  4. 群赛 round#8 解题报告一 (swop,ranwen,easy)

    群赛 round#8 解题报告一 赛制: OI 难度: noip T1 交换!交换!(swop) [问题描述] ljm喜欢交换物品,他觉得这样可以与更多人分享好的事物. 有一天,lzx给了ljm n本 ...

  5. 【解题报告系列】超高质量题单 + 题解(ACM / OI)超高质量题解

    整理的算法模板合集: ACM模板 点我看算法全家桶系列!!! 实际上是一个全新的精炼模板整合计划 繁凡出品的全新系列:解题报告系列 -- 超高质量算法题单,配套我新写的超高质量的题解和代码,题目难度不 ...

  6. 解题报告(五)组合计数(ACM / OI)超高质量题解

    繁凡出品的全新系列:解题报告系列 -- 超高质量算法题单,配套我写的超高质量题解和代码,题目难度不一定按照题号排序,我会在每道题后面加上题目难度指数(1∼51 \sim 51∼5),以模板题难度 11 ...

  7. 解题报告(三)多项式求值与插值(拉格朗日插值)(ACM / OI)

    整理的算法模板合集: ACM模板 点我看算法全家桶系列!!! 实际上是一个全新的精炼模板整合计划 繁凡出品的全新系列:解题报告系列 -- 超高质量算法题单,配套我写的超高质量的题解和代码,题目难度不一 ...

  8. 解题报告(十三)中国剩余定理(ACM / OI)

    整理的算法模板合集: ACM模板 点我看算法全家桶系列!!! 实际上是一个全新的精炼模板整合计划 繁凡出品的全新系列:解题报告系列 -- 超高质量算法题单,配套我写的超高质量的题解和代码,题目难度不一 ...

  9. 解题报告(二)多项式问题(多项式乘法及其各种运算)(ACM/ OI)超高质量题解

    整理的算法模板合集: ACM模板 点我看算法全家桶系列!!! 实际上是一个全新的精炼模板整合计划 繁凡出品的全新系列:解题报告系列 -- 超高质量算法题单,配套我写的超高质量的题解和代码,题目难度不一 ...

最新文章

  1. win10 中redis client提示 ERR Client sent AUTH,but no password is set
  2. 2021 IEEE热门AI话题盘点:模仿生物大脑打造神经网络、GPT3“不当言论”惹关注…...
  3. django captcha 验证码插件
  4. 成功当选2014年度MVP
  5. 使用struts 2 获取服务器数据 ongl表达式 标签
  6. 利用SSH端口转发功能实现X转发
  7. 数据库原理与应用(SQL Server)笔记 第七章 流程控制语句、系统内置函数
  8. php 编程祝新年快乐_第一门编程语言选什么好?
  9. mysql添加有效值_物理数据库设计 - 限定列的有效值
  10. linux major头文件_《Linux设备驱动程序》(四)——字符设备驱动(上)
  11. 盘古分词工具学习笔记
  12. 极品飞车8 免CD补丁 CDKEY生成器
  13. CTF中各种好用的软件以及解密网址合集
  14. android adb 命令启动,如何从adb shell启动和停止android服务?
  15. 【python】urlencode、quote、unquote
  16. python cox模型_Cox模型的基本概念_Python数据分析系列视频课程--玩转统计模型_数据挖掘与分析视频-51CTO学院...
  17. Hubstudio指纹浏览器和YiLu代理(易路代理)的配置教程
  18. data为什么是一个函数
  19. cancase vector_低價替代Vector CANoe CAN總線適配解決方案支持所有USBCAN(周立功CAN、PCAN、Kvaser、ValueCAN、NI CAN)...
  20. 北师大数据结构期末考试复习

热门文章

  1. 始于TensorFlow ,终于PyTorch
  2. 部分 II. Network
  3. 智能手机收邮件之NOKIA
  4. 报错——StackOverflowError
  5. 手工接口测试考虑的点
  6. 迭代var()内置函数的时候出现RuntimeError: dictionary changed size during iteration的解决办法...
  7. CentOS 6/7 忘记root密码,重新设置的方法
  8. jdbc与hibernate的优缺点比较
  9. user is not in the sudoers file.
  10. 用VirtualBox在XP环境下虚拟Ubuntu的过程