多项式例题

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

Luogu P4389 付公主的背包

Link

Description

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

1≤n,m≤105,1≤vi≤m1\le n,m \le 10^5,1\le v_i \le m

Solution

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

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

F(x)=∑i=0∞xv×i=11−xvF(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)(1−xv)=∑i=0∞vixvi−1(1−xv)=∑i=0∞vixvi−1−∑i=0∞vxv(i+1)−1=∑i=1∞vixvi−1−∑i=1∞v(i−1)xvi−1=∑i=1∞vxvi−1G(x)=∫G′(x)dx=∑i=1∞xvii\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)=e∑i=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(mlog⁡m)O(m\log m) 求多项式 exp,总复杂度 O(mlog⁡m)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 个点的的简单 (无重边无自环) 有标号无向连通图个数。

n≤130000n\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(n−1i−1)f(i)g(n−i)g(n)=\sum_{i=1}^n\binom{n-1}{i-1}f(i)g(n-i)

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

2(n2)=∑i=1n(n−1i−1)f(i)g(n−i)2(n2)=∑i=1n(n−1i−1)f(i)2(n−i2)2(n2)(n−1)!=∑i=1nf(i)2(n−i2)(i−1)!(n−i)!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=1∞f(n)(n−1)!G(x)=∑n=1∞2(n2)n!H(x)=∑n=1∞2(n2)(n−1)!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 的系数乘 (n−1)!(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×an−ia_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=1ngi∑j=in−ifjfn−i−j (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=G∗F2+1F=G*F^2+1

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

根据求根公式

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

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

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

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

F=21+1−4GF=\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 的基因串 SS 和 TT,求在下列规则中 TT 在 SS 中出现了几次。

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

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

Solution

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

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

然后我们将得到的新的 SS 和 TT 分别设为 ff 和 gg,都是 0101 串。

hi=∑j=0m−1gj(fi+j−gj)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=0m−1gj(fi+j−gj)2=∑j=0m−1gjfi+j2−gj3−2gj2fi+j=∑j=0m−1gj−gjfi+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=0m−1gm−1−j−gm−1−jfi+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 匹配。

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

Solution

经典通配符匹配问题

将 * 的位置赋成 0。

设 AA 和 BB 的差异为

d(A,B)=∑i=0n−1(Ai−Bi)2AiBid(A,B)=\sum_{i=0}^{n-1}(A_i-B_i)^2A_iB_i

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

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

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

f(i)=∑j=0i(Ai−j−Bj)2Ai−jBj=∑j=0iAi−j3Bj−Ai−j2Bj2+Ai−jBj3\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 的数组 aa 和 bb,求 ∑i=1n(ai+x−bi)2\sum_{i=1}^n(a_i+x-b_i)^2 的最小值。

1≤n≤50000,1≤ai≤m≤1001\le n \le 50000,1\le a_i \le m \le 100

Solution

将式子展开

∑i=1n(ai+x−bi)2=∑i=1n(ai2+bi2+x2+2aix−2aibi−2bix)=∑i=1nai2+∑i=1nbi2+∑i=1nx2+2x∑i=1n(ai−bi)−2∑i=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=∑(bi−ai)nx=\dfrac{\sum(b_i-a_i)}{n}

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

也是套路地翻转一下 bb 数组,然后就可以卷积了,要将 aa 数组复制一倍,卷积后求 xnx^n 到 x2n−1x^{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.