专栏文章

浅谈分治求和法

算法·理论参与者 12已保存评论 15

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
15 条
当前快照
1 份
快照标识符
@mjx4epuf
此快照首次捕获于
2026/01/03 01:01
2 个月前
此快照最后确认于
2026/02/19 01:22
10 小时前
查看原文
这个技巧是我打 dmy 选拔时 zxf 告诉我的,由于笔者找不到记录这个 trick 的文章,因此本文将其称之为“分治求和法”。
分治求和法的主要功能,是在 O(logn)O(\log n) 内解决一类形如 i=1nfixi\sum \limits_{i=1}^n f_i x^i 的求和问题,常见如等比数列,等差比数列等。
对于等比数列求和,通常我们可以直接套用公式,但那样往往需要模数下的逆元。如果题目给定的模数不是质数,就无法直接使用公式。而分治求和法则完全不依赖逆元,支持任意模数。

等比数列和

考虑等比数列和
S(n)=i=1nxiS(n)=\sum_{i=1}^{n} x^i
nn 的奇偶性进行讨论:
  • nn 为奇数:
S(n)=i=1nxi=xn+i=1n1xi=xn+S(n1)\begin{aligned} S(n)&=\sum_{i=1}^{n} x^i\\ &=x^n+\sum_{i=1}^{n-1} x^i\\ &=x^n+S(n-1) \end{aligned}
  • nn 为偶数:
S(n)=i=1nxi=i=1n/2xi+i=n/2+1nxi=S(n/2)+xn/2i=1n/2xi=(1+xn/2)S(n/2)\begin{aligned} S(n)&=\sum_{i=1}^{n} x^i\\ &=\sum_{i=1}^{n/2} x^i+\sum_{i=n/2+1}^{n} x^i\\ &=S(n/2)+x^{n/2} \sum_{i=1}^{n/2} x^i\\ &=\left(1+x^{n/2}\right) S(n/2) \end{aligned}
递归分治即可,复杂度 O(log2n)O(\log^2 n)

P10502 Matrix Power Series

i=0kAi\sum_{i=0}^k A^i
其中 AA 是一个矩阵。对给出的模数取模。
n30,k109n \le 30, k\le 10^9。1 秒,512 MB。
直接套上分治求和即可。

高次项求和

相信你已经理解了分治求和法的基本思想,来看看下面的扩展:

P4948 数列求和

Sn(k)=i=1kinxiS_{n}(k)=\sum_{i=1}^k i^n x^i
n2000n \le 2000k1018k \le 10^{18},1 秒,512 MB。
本来我打算把这个题放到入校测试 T2 的。结果 hwh 告诉我原了,我过了以后发现没有用分治求和做的(
仍按 kk 的奇偶性进行讨论。
  1. kk 为奇数时,有
Sn(k)=i=1kinxi=x+i=2kinxi=x+xi=1k1(i+1)nxi=x+xi=1k1xij=0n(nj)ij=x+xj=0n(nj)i=1k1ijxi=x+xj=0n(nj)Sj(k1)\begin{aligned} S_{n}(k)&=\sum_{i=1}^k i^n x^i\\ &=x +\sum_{i=2}^{k} i^n x^i\\ &=x+x\sum_{i=1}^{k-1} (i+1)^n x^i \\ &=x+x\sum_{i=1}^{k-1}x^i \sum_{j=0}^n \binom{n}{j} i^j\\ &=x+x\sum_{j=0}^n \binom{n}{j} \sum_{i=1}^{k-1} i^j x^i \\ &= x+x\sum_{j=0}^n \binom{n}{j} S_{j}(k-1) \end{aligned}
  1. kk 为偶数时,有
Sn(k)=i=1kinxi=i=1k/2inxi+i=k/2+1kinxi=Sn(k/2)+xk/2i=1k/2(i+k/2)nxi=Sn(k/2)+xk/2i=1k/2xij=0n(nj)ij(k/2)nj=Sn(k/2)+xk/2j=0n(nj)(k/2)nji=1k/2ijxi=Sn(k/2)+xk/2+j=0n(nj)(k/2)njSj(k/2)\begin{aligned} S_{n}(k)&=\sum_{i=1}^k i^n x^i\\ &=\sum_{i=1}^{k/2} i^n x^i+\sum_{i=k/2+1}^{k} i^n x^i \\ &=S_n(k/2)+x^{k/2} \sum_{i=1}^{k/2} (i+k/2)^n x^i\\ &=S_n(k/2)+x^{k/2} \sum_{i=1}^{k/2} x^i \sum_{j=0}^n \binom{n}{j} i^j (k/2)^{n-j}\\ &=S_n(k/2)+x^{k/2} \sum_{j=0}^n \binom{n}{j} (k/2)^{n-j}\sum_{i=1}^{k/2} i^j x^i \\ &=S_n(k/2)+x^{k/2} + \sum_{j=0}^n \binom{n}{j} (k/2)^{n-j} S_j(k/2) \end{aligned}
递归求解即可,边界为 k=1k=1,此时 Sn(1)=xS_n(1)=x
注意:偶数情况中 (k/2)nj(k/2)^{n-j} 不能对每个 jj 单独快速幂计算,否则复杂度会退化为 O(n2log2k)O(n^2 \log^2 k)。只需倒序枚举 jj,并维护 k/2k/2 的幂次,即可将复杂度优化至 O(n2logk)O(n^2 \log k)
如果使用杨辉三角递推组合数,该做法支持任意模数。
CPP
constexpr int N = 2005;

long long n, k;
mint x, c[N][N];

void prework() {
	for (int i = 0; i <= n; i++) c[i][0] = c[i][i] = 1;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j < i; j++) c[i][j] = c[i - 1][j] + c[i - 1][j - 1];
	}
}

vector<mint> solve(long long k) {
	if (k == 1) return vector<mint>(n + 1, x);
	vector<mint> S(n + 1);
	if (k & 1) {
		auto T = solve(k - 1);
		for (int i = 0; i <= n; i++) {
			mint cur = 0;
			for (int j = 0; j <= i; j++) cur += c[i][j] * T[j];
			S[i] = x + x * cur;
		}
		return S;
	} else {
		auto T = solve(k >> 1);
		mint p = x.pow(k >> 1);
		for (int i = 0; i <= n; i++) {
			mint cur = 0, b = k >> 1, pw = 1;
			for (int j = i; j >= 0; j--, pw *= b) cur += c[i][j] * T[j] * pw;
			S[i] = T[i] + p * cur;
		}
		return S;
	}
}

void _main() {
	cin >> k >> x >> n;
	prework();
	auto S = solve(k);
	cout << S[n];
}

P4102 [HEOI2014] 林中路径

省流:求
S(k)=i=1ki2GiS(k)=\sum_{i=1}^k i^2 G^i
其中 GG 为邻接矩阵。
套上上面的做法求出 S2S_2,你就秒了一个黑题。

P3216 [HNOI2011] 数学作业

C(n)=C(n1)×101+lgn+nC(n)=C(n-1) \times 10^{1+ \left\lfloor \lg n\right\rfloor }+n
特别地 C(1)=1C(1)=1。对给出的模数取模。
n1018 n \le 10^{18},1 秒,512 MB。
定义函数 f(l,r)f(l,r) 表示将区间 [l,r][l,r] 中所有数字依次连接形成的数(llrr 的位数相同)。
可以通过 O(logn)O(\log n) 枚举位数得到 C(n)=f(l,r)C(n)=\sum f(l,r)。写出 f(l,r)f(l,r) 的解析式
f(l,r)=i=lri×10rif(l,r)=\sum_{i=l}^r i \times 10^{r-i}
仿照求解 S1(rl)S_1(r-l) 的方法即可求出 f(l,r)f(l,r),总时间复杂度 O(log3n)O(\log^3 n)

评论

15 条评论,欢迎与作者交流。

正在加载评论...