分治 分治的核心一句话:把大问题拆成形式完全一致的小问题,直到问题足够简单。 听起来简单,但「怎么拆」往往才是难点。
CF 1385D a-Good String Problem - D - Codeforces
每次折半:左边一半全部改成 c,右边一半递归下去变成 c+1 开头的 good string;再和反过来的方案取 min。原题「折半」表达得很明显,不往分治想确实不应该。
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 #include <bits/stdc++.h> using namespace std;int solve (string& s, int l, int r, char c) { if (l == r) return (s[l] != c); int mid = (l + r) / 2 ; int cnt1 = 0 ; for (int i = l; i <= mid; i++) if (s[i] != c) cnt1++; cnt1 += solve (s, mid + 1 , r, c + 1 ); int cnt2 = 0 ; for (int i = mid + 1 ; i <= r; i++) if (s[i] != c) cnt2++; cnt2 += solve (s, l, mid, c + 1 ); return min (cnt1, cnt2); } int main () { ios::sync_with_stdio (false ); cin.tie (0 ); int t; cin >> t; while (t--) { int n; cin >> n; cin.ignore (); string s; getline (cin, s); cout << solve (s, 0 , n - 1 , 'a' ) << endl; } return 0 ; }
P1228 地毯填补(棋盘覆盖) P1228 地毯填补问题 - 洛谷
这道题即使我知道是分治标签也无从下手,光看样例示意图就足够眼花。后面发现核心在于人为构造出「虚拟的公主格」 :
不是去填补空缺,而是主动制造特殊点 ,让问题始终保持在「一个特殊点」这个可控状态。
中心那块地毯的三个延伸部分,恰好为其他三个区域「创造」了继续递归的条件。没有公主格的区域,也能靠虚拟格无缝套进同一个分治框架。这其实是经典的棋盘覆盖问题 (Golomb, 1954):一个 2^n × 2^n 的棋盘去掉一格,能用 L 型骨牌完美覆盖。
难点 :人为构造虚拟公主格这个想法我想不到。分治本身不难,难在「如何让每个子问题的形态保持一致」。
分治求等比数列和 对于 $S(p, c) = 1 + p + p^2 + \dots + p^c$(模 MOD),分治可以在 $O(\log c)$ 内求出:
$c$ 为奇数:$S(p,c) = (1 + p^{(c+1)/2}) \cdot S(p, (c-1)/2)$
$c$ 为偶数:$S(p,c) = (1 + p^{c/2}) \cdot S(p, c/2 - 1) + p^c$
配合快速幂,就能处理「质因数分解后,每个因子次幂求和再相乘」这一类问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 using ll = long long ;const int MOD = 9901 ;ll qpow (ll base, ll exp) { ll res = 1 ; base %= MOD; while (exp > 0 ) { if (exp & 1 ) res = res * base % MOD; base = base * base % MOD; exp >>= 1 ; } return res; } ll sum (ll p, ll c) { if (c == 0 ) return 1 ; if (c & 1 ) { return (1 + qpow (p, (c + 1 ) >> 1 )) % MOD * sum (p, (c - 1 ) >> 1 ) % MOD; } else { return ((1 + qpow (p, c >> 1 )) % MOD * sum (p, (c >> 1 ) - 1 ) % MOD + qpow (p, c)) % MOD; } }
总结 分治的关键不是「递归自己调自己」,而是:
子问题形式完全一致 ,否则没法递归下去。
必要时主动构造条件 (虚拟公主格)让子问题统一。
递归树对称时,往往能写出 $O(\log n)$ 甚至 $O(\log^2 n)$ 的漂亮算法。