多项式例题

一些多项式以及生成函数的题目

Luogu P4389 付公主的背包

Link

Description

nn 种物品,每种物品体积为 vv,每种商品有无数个,求凑出体积为 mm 的方案数。

1n,m105,1vim1\le n,m \le 10^5,1\le v_i \le m

Solution

需要一些生成函数的知识。

对于每个物品我们知道他的生成函数长这样

F(x)=i=0xv×i=11xvF(x)=\sum_{i=0}^{\infty}x^{v\times i}=\frac{1}{1-x^v}

就是在 vv 的倍数处为 11,其余为 00。封闭形式只需要向右移动 vv 位,也就是乘 xvx^v

暴力来做就把这 nn 个生成函数卷起来就行了。

i=1nF(x)\prod_{i=1}^n F(x)

考虑优化。在麦克劳林级数定义下的 ln\ln 可以进行正常运算,我们可以把卷积这种复杂度较高的式子优化为加法。

ln (i=1nF(x))=i=1nln(F(x))\ln\ (\prod_{i=1}^n F(x) )=\sum_{i=1}^n\ln(F(x))

G(x)=ln(F(x))G(x)=\ln(F(x))

大力推式子

G(x)=ln(F(x))=F(x)F(x)=F(x)(1xv)=i=0vixvi1(1xv)=i=0vixvi1i=0vxv(i+1)1=i=1vixvi1i=1v(i1)xvi1=i=1vxvi1G(x)=G(x)dx=i=1xvii\begin{aligned} G'(x)&=\ln'(F(x))\\ &=\frac{F'(x)}{F(x)}\\ &=F'(x)(1-x^v)\\ &=\sum_{i=0}^{\infty}vix^{vi-1}(1-x^v)\\ &=\sum_{i=0}^{\infty}vix^{vi-1}-\sum_{i=0}^{\infty}vx^{v(i+1)-1}\\ &=\sum_{i=1}^{\infty}vix^{vi-1}-\sum_{i=1}^{\infty}v(i-1)x^{vi-1}\\ &=\sum_{i=1}^{\infty}vx^{vi-1}\\ G(x)=&\int G'(x)dx=\sum_{i=1}^{\infty}\frac{x^{vi}}{i} \end{aligned}

答案即为

i=1nF(x)=i=1neG(x)=ei=1nG(x)\prod_{i=1}^n F(x)=\prod_{i=1}^n e^{G(x)} = e^{\sum_{i=1}^nG(x)}

我们只需要多项式的前 mm 项,O(m)O(m)i=1nG(x)\sum_{i=1}^n G(x)O(mlogm)O(m\log m) 求多项式 exp,总复杂度 O(mlogm)O(m\log m)

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
int n, m, tab[N];
ll inv[N], f[N << 2], g[N << 2];

int main()
{
read(n), read(m);
for(int i = 1, v; i <= n; i++)
read(v), tab[v]++;

inv[1] = 1;
for(int i = 2; i <= m; i++)
inv[i] = (p - p / i) * inv[p % i] % p;

for(int i = 1; i <= m; i++)
{
if(!tab[i]) continue;
ll tmp = i * tab[i] % p;
for(int j = i; j <= m; j += i)
g[j] = add(g[j] + tmp);
}
for(int i = 0; i <= m; i++)
g[i] = g[i] * inv[i] % p;

Exp(g, f, m + 1);

for(int i = 1; i <= m; i++)
write(f[i]), pc('\n');

return 0;
}
// A.S.

Luogu P4841 [集训队作业2013]城市规划

Link

Description

nn 个点的的简单 (无重边无自环) 有标号无向连通图个数。

n130000n\le 130000

Solution

f(n)f(n) 表示 nn 个点的的简单有标号无向连通图个数,g(n)g(n) 表示 nn 个点的有标号无向图个数。

显然 g(n)=2(n2)g(n)=2^{\binom{n}{2}},因为共有 (n2)\binom{n}{2} 条边,每条边都可以选或不选。

g(n)=i=1n(n1i1)f(i)g(ni)g(n)=\sum_{i=1}^n\binom{n-1}{i-1}f(i)g(n-i)

可以理解成枚举 11 所在的连通块有几个点。

2(n2)=i=1n(n1i1)f(i)g(ni)2(n2)=i=1n(n1i1)f(i)2(ni2)2(n2)(n1)!=i=1nf(i)2(ni2)(i1)!(ni)!2^{\binom{n}{2}}=\sum_{i=1}^n\binom{n-1}{i-1}f(i)g(n-i) \\ 2^{\binom{n}{2}}=\sum_{i=1}^n\binom{n-1}{i-1}f(i)2^{\binom{n-i}{2}} \\ \dfrac{2^{\binom{n}{2}}}{(n-1)!}=\sum_{i=1}^n\dfrac{f(i)2^{\binom{n-i}{2}}}{(i-1)!(n-i)!}

然后我们其实已经有卷积的形式了。

F(x)=n=1f(n)(n1)!G(x)=n=12(n2)n!H(x)=n=12(n2)(n1)!F(x)=\sum_{n=1}^\infin\dfrac{f(n)}{(n-1)!} \\ G(x)=\sum_{n=1}^\infin\dfrac{2^{\binom{n}{2}}}{n!} \\ H(x)=\sum_{n=1}^\infin\dfrac{2^{\binom{n}{2}}}{(n-1)!}

那么

H(x)=F(x)G(x)F(x)=H(x)G(x)H(x)=F(x)*G(x) \\ F(x)=\dfrac{H(x)}{G(x)}

最后的答案即为 F(x)F(x)xnx^n 的系数乘 (n1)!(n-1)!

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int n;
ll fac[N];
ll f[N << 2], g[N << 2], gi[N << 2];

int main()
{
read(n), n++;

fac[0] = 1, g[0] = 1;
for(int i = 1; i < n; i++) fac[i] = fac[i - 1] * i % p;
for(int i = 1; i < n; i++) f[i] = qpow(2, 1ll * i * ( i - 1 ) / 2 % (p - 1)) * qpow(fac[i - 1], p - 2) % p;
for(int i = 1; i < n; i++) g[i] = f[i] * qpow(i, p - 2) % p;

Inv(g, gi, n);
Mul(f, gi, n, n);

write(f[n - 1] * fac[n - 2] % p), pc('\n');
return 0;
}
// A.S.

Luogu P4723 【模板】常系数齐次线性递推

Link

Description

求一个满足 kk 阶齐次线性递推数列 ai{a_i} 的第 nn 项,即:

an=i=1kfi×ania_n=\sum\limits_{i=1}^{k}f_i \times a_{n-i}

998244353998244353 取模

n=109,k=32000n=10^9,k=32000

Solution

懒得写了,直接放一下连接吧(,讲的太好了

题解 P4723 【模板】常系数齐次线性递推

但是题解区代码没一个能看的(

主要是注意一下边界,在乘法后项数会多一倍,所以取模时传的数组长度要为 2m2m

不知道为啥常数非常大,不开 O2 一个点都过不去,可能是人傻常数大吧 qaq

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
int n, m;
ll p[N], f[N], pr[N], pi[N]; //p: f, pr: rev(p), pi: inv(pr), f: a
ll a[N], b[N];

void Mod(ll *a, ll *b, int n, int m)
{
int lim = calclim(n << 1);
Copy(a, b, n), Clear(b, n, lim), reverse(b, b + n);
Mul(b, pi, n, n - m + 2), Clear(b, n - m + 1, n + n - m + 2), reverse(b, b + n - m + 1);
Mul(b, p, n - m + 2, m);
for(int i = 0; i < m - 1; i++) b[i] = sub(a[i] - b[i]);
return;
}

ll r[N];

void MulMod(ll *a, ll *b, int n, int m)
{
int lim = calclim(n << 1);
Clear(r, 0, lim);
Mul(a, b, n, m);
Mod(a, r, n + m, m);
Copy(r, a, m - 1), Clear(a, m - 1, lim);
return;
}

void Qpow(ll *a, ll *b, int k)
{
b[0] = 1;
while(k)
{
if(k & 1) MulMod(b, a, m, m);
MulMod(a, a, m, m), k >>= 1;
}
return;
}

int main()
{
read(n), read(m);
for(int i = 1; i <= m; i++) read(pr[i]), pr[i] = sub(mod - pr[i] % mod), p[m - i] = pr[i];
for(int i = 0; i < m; i++) read(f[i]);
p[m] = pr[0] = 1, m++;
Inv(pr, pi, m + 2);

a[1] = 1;
Qpow(a, b, n);

ll ans = 0;
for(int i = 0; i < m; i++)
ans = (ans + f[i] * b[i] % mod + mod) % mod;
write(ans), pc('\n');

return 0;
}
// A.S.

CF438E The Child and Binary Tree

Link

Description

太长不想写(

Solution

fif_i 表示权值为 ii 的神犇二叉树的个数,即为所求,gig_i 表示有没有权值为 ii 的点。

gi=0/1fn=1 (n=0)fn=i=1ngij=inifjfnij (n>0)g_i=0/1\\ f_n=1\ (n=0) \\ f_n=\sum_{i=1}^ng_i\sum_{j=i}^{n-i}f_{j}f_{n-i-j}\ (n>0) \\

可以发现这是三个多项式卷在一起

FF 表示 ff 的生成函数,GG 表示 gg 的生成函数

F=GF2+1F=G*F^2+1

这里 +1+1 是因为 f0=1f_0=1

根据求根公式

F=1±14G2GF=\dfrac{1\pm \sqrt{1-4G}}{2G}

分类讨论一下 ±\pm,发现只能是 -,如果发现不了就都试一试就行了(

F=114G2GF=\dfrac{1-\sqrt{1-4G}}{2G}

分子分母同时 ×(1+14G)\times (1+\sqrt{1-4G}),得

F=21+14GF=\dfrac{2}{1+\sqrt{1-4G}}

这波分子无理化也属实 nb,因为 2G2G 的常数项为 00,并不能求逆,而后面的式子有个 +1+1

然后就是多项式求逆和多项式开根板子了

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int n, m;
ll f[N << 2], g[N << 2], sg[N << 2];

int main()
{
read(n), read(m), m++;
for(int i = 1, c; i <= n; i++)
read(c), g[c] = 1;

g[0] = 1;
for(int i = 1; i < m; i++)
g[i] = p - 4 * g[i] % p;

Sqrt(g, sg, m);
sg[0]++;
Inv(sg, f, m);
for(int i = 0; i < m; i++) f[i] = f[i] * 2 % p;

for(int i = 1; i < m; i++) write(f[i]), pc('\n');
pc('\n');

return 0;
}
// A.S.

Link

Description

给定两个只含 AGCT 的基因串 SSTT,求在下列规则中 TTSS 中出现了几次。

  • TTSS 的第 ii 个位置中出现,当且仅当把 TT 的首字符和 SS 的第 ii 个字符对齐后,TT 中的每一个字符能够在 SS 中找到一个位置偏差不超过 kk 的相同字符。

1nm2×105,0k2×1051\le n\le m \le 2\times 10^5,0\le k \le 2\times 10^5

Solution

因为只有四种字符,我们不妨对每个字符分别考虑,如果四种字符都合法,那么这个位置就是合法的。

对于偏差不超过 kk,并不太好处理,但是我们只对一个字符考虑,所以可以将可以为该字符的位置赋成 11,其余的都赋成 00,即把在偏差 kk 以内的都改成该字符。

然后我们将得到的新的 SSTT 分别设为 ffgg,都是 0101 串。

hi=j=0m1gj(fi+jgj)2h_i=\sum_{j=0}^{m-1}g_j(f_{i+j}-g_j)^2

如果 hi=0h_i=0,就说明可以匹配。

平方是为了不会出现 1+(1)=01+(-1)=0 的情况

hi=j=0m1gj(fi+jgj)2=j=0m1gjfi+j2gj32gj2fi+j=j=0m1gjgjfi+j\begin{aligned} h_i&=\sum_{j=0}^{m-1}g_j(f_{i+j}-g_j)^2 \\ &=\sum_{j=0}^{m-1}g_jf_{i+j}^2-g_j^3-2g_j^2f_{i+j} \\ &=\sum_{j=0}^{m-1}g_j-g_jf_{i+j} \end{aligned}

这里是因为 f,gf,g 都为 0101 串,所以三次方和平方都可以改为一次。

然后就套路地将 gg 翻转,就可以卷积了

hi=j=0m1gm1jgm1jfi+jh_i=\sum_{j=0}^{m-1}g_{m-1-j}-g_{m-1-j}f_{i+j}

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
int n, m, k, lim;
char s[N], t[N];
ll f[N << 2], g[N << 2], h[N << 2];
int len[N];

void solve(char c)
{
memset(f, 0, sizeof(f));
memset(g, 0, sizeof(g));
memset(h, 0, sizeof(h));

for(int i = 0; i < n; i++)
if(s[i] == c) f[i - k < 0 ? 0 : i - k]++, f[i + k + 1]--;
for(int i = 0; i < n; i++) f[i] += f[i - 1];
for(int i = 0; i < n; i++) if(f[i]) f[i] = 1;

for(int i = 0; i < m; i++) g[i] = t[m - i - 1] == c;

Mul(f, g, n, m);

for(int i = 0; i < n; i++) len[i] += f[i];
return;
}

int main()
{
read(n), read(m), read(k);
scanf("%s%s", s, t);

solve('A'), solve('C'), solve('G'), solve('T');

int ans = 0;
for(int i = 0; i < n; i++)
ans += (len[i] == m);
write(ans), pc('\n');

return 0;
}
// A.S.

Luogu P4173 残缺的字符串

Link

Description

给定一个长度为 nn 的字符串 AA 和一个长度为 mm 的字符串 BB,仅由小写字母和 * 组成,其中 * 表示相应位置已经残缺,即可以为任意字母。

求对于 BB 的每一个位置 ii,从这个位置开始连续 nn 个字符形成的子串是否可能与 AA 匹配。

1nm3×1051\le n\le m \le 3\times 10^5

Solution

经典通配符匹配问题

将 * 的位置赋成 0。

AABB 的差异为

d(A,B)=i=0n1(AiBi)2AiBid(A,B)=\sum_{i=0}^{n-1}(A_i-B_i)^2A_iB_i

那么 BBii 结尾的子串与 AA 完全匹配的条件为

f(i)=d(A,B[im+1,i])=0f(i)=d(A,B[i-m+1,i])=0

比较套路地翻转 AA,并写成卷积的形式

f(i)=j=0i(AijBj)2AijBj=j=0iAij3BjAij2Bj2+AijBj3\begin{aligned} f(i)&=\sum_{j=0}^{i}(A_{i-j}-B_j)^2A_{i-j}B_j \\ &=\sum_{j=0}^iA_{i-j}^3B_j-A_{i-j}^2B_j^2+A_{i-j}B_j^3 \end{aligned}

然后就是三个卷积了,这里写的 FFT

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
int n, m;
char S[N], T[N];
int s[N], t[N];
Complex a[N << 2], b[N << 2], c[N << 2];

int main()
{
read(n), read(m);
scanf("%s%s", S, T);
for(int i = 0; i < n; i++) if(S[i] != '*') s[i] = S[i] - 'a' + 1;
for(int i = 0; i < m; i++) if(T[i] != '*') t[i] = T[i] - 'a' + 1;
reverse(s, s + n);

int lim = calclim(n + m);
calcrev(lim);

for(int i = 0; i < lim; i++) a[i] = b[i] = Complex(0.0, 0.0);
for(int i = 0; i < n; i++) a[i] = Complex(s[i] * s[i] * s[i], 0);
for(int i = 0; i < m; i++) b[i] = Complex(t[i], 0);
FFT(a, lim, 1), FFT(b, lim, 1);
for(int i = 0; i < lim; i++) c[i] = a[i] * b[i];

for(int i = 0; i < lim; i++) a[i] = b[i] = Complex(0.0, 0.0);
for(int i = 0; i < n; i++) a[i] = Complex(s[i] * s[i], 0);
for(int i = 0; i < m; i++) b[i] = Complex(t[i] * t[i], 0);
FFT(a, lim, 1), FFT(b, lim, 1);
for(int i = 0; i < lim; i++) c[i] = c[i] - a[i] * b[i] * 2.0;

for(int i = 0; i < lim; i++) a[i] = b[i] = Complex(0.0, 0.0);
for(int i = 0; i < n; i++) a[i] = Complex(s[i], 0);
for(int i = 0; i < m; i++) b[i] = Complex(t[i] * t[i] * t[i], 0);
FFT(a, lim, 1), FFT(b, lim, 1);
for(int i = 0; i < lim; i++) c[i] = c[i] + a[i] * b[i];
FFT(c, lim, -1);

int ans = 0;
for(int i = n - 1; i < m; i++)
if(fabs(c[i].x) < 0.5) ans++;
write(ans), pc('\n');
for(int i = n - 1; i < m; i++)
if(fabs(c[i].x) < 0.5) write(i - n + 2), pc(' ');
pc('\n');

return 0;
}

Luogu P3723 [AH2017/HNOI2017]礼物

Link

Description

给定两个长度为 nn 的数组 aabb,求 i=1n(ai+xbi)2\sum_{i=1}^n(a_i+x-b_i)^2 的最小值。

1n50000,1aim1001\le n \le 50000,1\le a_i \le m \le 100

Solution

将式子展开

i=1n(ai+xbi)2=i=1n(ai2+bi2+x2+2aix2aibi2bix)=i=1nai2+i=1nbi2+i=1nx2+2xi=1n(aibi)2i=1naibi\begin{aligned} &\sum_{i=1}^n(a_i+x-b_i)^2 \\ =&\sum_{i=1}^n(a_i^2+b_i^2+x^2+2a_ix-2a_ib_i-2bi_x) \\ =&\sum_{i=1}^na_i^2+\sum_{i=1}^nb_i^2+\sum_{i=1}^nx^2+2x\sum_{i=1}^n(a_i-b_i)-2\sum_{i=1}^na_ib_i \end{aligned}

一二项是定值,三四项就是个二次函数,只需要找到对称轴,然后找到两边最近的两个整数,算一下最小值即可。

x=(biai)nx=\dfrac{\sum(b_i-a_i)}{n}

现在只需要将第五项最大化

也是套路地翻转一下 bb 数组,然后就可以卷积了,要将 aa 数组复制一倍,卷积后求 xnx^nx2n1x^{2n-1} 的系数的最大值。

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
int n, m;
ll a[N], b[N], sa, sb, ans;

ll calc(int x)
{
return n * x * x + 2 * x * (sa - sb);
}

int main()
{
read(n), read(m);
for(int i = 0; i < n; i++) read(a[i]), a[i + n] = a[i], sa += a[i], ans += a[i] * a[i];
for(int i = 0; i < n; i++) read(b[i]), sb += b[i], ans += b[i] * b[i];

int x1 = floor(1.0 * (sb - sa) / n), x2 = ceil(1.0 * (sb - sa) / n);
ans += min(calc(x1), calc(x2));

reverse(b, b + n);
Mul(a, b, n + n, n);

ll mx = -1e18;
for(int i = n; i < n * 2; i++) mx = max(mx, a[i]);
ans -= (mx << 1);
write(ans), pc('\n');
return 0;
}
// A.S.