组合数学题,递推的优化思路很值得学习。
难度普及+/提高
算法组合数学、递推
日期2025-11-20
题意简述
小 F 有一个数列 {x1,x2,…,xn},其中 xi∈N∗ 且 xi≤v。现在小 F 为这个数列添加了 n−1 条 A 型限制和 m 条 B 型限制。
每条 A 型限制 (ai,bi) 的意思是:
- 若 xi=ai,要求 xi+1=bi。
- 若 xi=ai,无限制。
- ai,bi∈N∗ 且 ai,bi≤v。
每条 B 型限制 (ci,di) 的意思是:
- 要求 xci=di。
- ci,di∈N∗ 且 ci≤n,di≤v。
小 F 记住了所有 cj 和 dj 的值,但把所有 ai 和 bi 的值都忘了。
现在小 F 想知道,有多少种 ai,bi 取值的组合,使得能够确保至少存在一个数列满足所有限制。方案数对 M=109+7 取模。(不保证一定有解)
范围:1≤n≤109, 1≤m≤105, 2≤v≤109.
一看这个 n 这么大,不难想到解法应大约是 O(logn) 左右的级别,而且很有可能是递推(因为 109 存不下,没法保存 DP 状态)。
考虑一个区间 [1,k],其中 1,k 位置上都存在 B 型限制(即端点的值被固定),而中间的位置没有。显然,此区间长度为 k,区间内共有 k−1 个 A 型限制。设在区间内填充 A 型限制(并有解)的总方案数为 f(k),那么数列的所有项就以 B 型限制的位置分成若干子区间,总方案数就是这些子区间的方案数之积。
现在我们考虑如何从 f(k−1) 推到 f(k)。
- 如果令 a1= 左端点值,则 b1 有 v 种取法。每种取法下,又相当于固定了位置 2,求 [2,k] 的方案数,等价于求 [1,k−1] 的方案数。因而在这种情况下有 v×f(k−1) 种方案。
- 如果令 a1= 左端点值(也就是限制不生效),则这些性质就与左端点“脱钩”了,随便填,一定有解。
- 位置 1 上的限制,a1 由于不能和左端点相同,故有 v(v−1) 种方案。
- 其他 k−2 个位置,a,b 都随便选,故有 v2 种方案。
- 全部乘起来,有 v(v−1)×(v2)k−2=v2k−2−v2k−3。
- 边界情况:[1,2] 时,有 1+v(v−1)=v2−v+1 种方案。
综上,有:
f(k)={v2−v+1vf(k−1)+v2k−2−v2k−3k=2,k>2.
直接这样做,单个区间的时间复杂度为 O(nlogn),总共 O(mnlogn),不能通过所有测试点。
数学 time
试着展开:
f(k−1)=vf(k−2)+v2(k−1)−2−v2(k−1)−3=vf(k−2)+v2k−4−v2k−5.
代入:
f(k)=v[vf(k−2)+v2k−4−v2k−5]+v2k−2−v2k−3=v2f(k−2)+v2k−3−v2k−4+v2k−2−v2k−3=v2f(k−2)−v2k−4+v2k−2.
再展开:
f(k−2)=vf(k−3)+v2(k−2)−2−v2(k−2)−3=vf(k−3)+v2k−6−v2k−7.
再代入:
f(k)=v2[vf(k−3)+v2k−6−v2k−7]−v2k−4+v2k−2=v3f(k−3)+v2k−4−v2k−5−v2k−4+v2k−2=v3f(k−3)−v2k−5+v2k−2.
看出来了吗?让我们归纳一下:
f(k)⇒f(k)=v1f(k−1)−v2k−3+v2k−2=v2f(k−2)−v2k−4+v2k−2=v3f(k−3)−v2k−5+v2k−2.=vpf(k−p)−v2k−p−2+v2k−2.
令 k−p=2,即 p=k−2,代入:
f(k)=vk−2f(2)−vk+v2k−2=vk−2(v2−v+1)−vk+v2k−2=vk−vk−1+vk−2−vk+v2k−2=v2k−2−vk−1+vk−2.
我们便可以用这个式子 + 快速幂在 O(mlogn) 的时间内求出答案。
边界情况
当 c1>1 时,会出现一个区间 [1,c1](长度为 c1,有 c1−1 个 A 型限制),其左端点无限制,右端点有限制。如何求这个区间的方案数?注意到每个位置上的限制其实可以随便取(和之前讨论的 a1= 左端点类似),故总方案数就是 (v2)c1−1=v2c1−2。
类似地,当 cm<n 时,会出现一个区间 [cm,n](长度为 n−cm+1,有 n−cm 个 A 型限制),方案数为 v2n−2cm。
注意判断 B 型限制有矛盾的时候方案数为 0、取模前要 +M。
用时 653 ms 内存 1.91 MB
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
const ull MOD = 1e9 + 7;
const ull MAXN = 1e9, MAXM = 1e5;
struct Rule {
int c, d;
bool operator<(const Rule& another) const {
return c < another.c;
}
} r[MAXM + 1];
ull qpow(ull a, ull b) {
ull res = 1;
while (b) {
if (b & 1) res = (res * a) % MOD;
a = (a * a) % MOD;
b >>= 1;
}
return res;
}
ull T, n, m, v;
ull foo(ull k) {
ull tot = 1;
if (k > 2) {
tot = (qpow(v, 2 * k - 2) + qpow(v, k - 2)) % MOD;
tot = (tot - qpow(v, k - 1) + MOD) % MOD;
} else if (k == 2) {
tot = (v * v - v + 1) % MOD;
}
return tot;
}
int main() {
cin >> T;
while (T--) {
cin >> n >> m >> v;
for (int i = 1; i <= m; i++) cin >> r[i].c >> r[i].d;
sort(r + 1, r + m + 1);
ull k, ans = 1;
if (r[1].c > 1) {
ans = (ans * qpow(v, 2 * r[1].c - 2)) % MOD;
}
for (int i = 2; i <= m; i++) {
k = r[i].c - r[i - 1].c + 1;
if (r[i].c == r[i - 1].c && r[i].d != r[i - 1].d) {
ans = 0;
break;
}
ans = (ans * foo(k)) % MOD;
}
if (r[m].c < n) {
ans = (ans * qpow(v, 2 * n - 2 * r[m].c)) % MOD;
}
cout << ans << '\n';
}
return 0;
}
本文思路受 题解:P11362 [NOIP2024] 遗失的赋值 启发。