[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"consumer-news-detail-854":3,"consumer-news-interaction-854":40,"consumer-news-related-854":43},{"detail":4,"item":35},{"card":5,"schemaVersion":22,"fields":23,"content":29},{"id":6,"kind":7,"targetType":8,"targetId":9,"subtype":7,"typeLabel":10,"title":11,"subtitle":12,"summary":13,"coverUrl":14,"badgeText":14,"href":15,"sourceName":12,"meta":16,"metrics":19,"tags":20,"resolved":21},"NEWS_ARTICLE:854","news","NEWS_ARTICLE",854,"资讯","分治：序列分治（CDQ）与点分治","博客园","分治：序列分治（CDQ）与点分治 一、分治思想概述 分治（Divide and Conquer）是算法设计中最核心的思想之一。它的基本策略是： 分（Divide）：将原问题划分为规模更小的子问题。 治（Conquer）：递归地求解子问题（若子问题足够小则直接求解）。 合（Combine）：将子问题的","","\u002Fnews\u002F854",[17,18],"2026","综合技术",{},[18],true,"consumer-content-detail-v1",{"sourceName":12,"authorName":24,"categoryName":18,"summary":13,"description":13,"publishTime":25,"updateTime":26,"sourceUrl":27,"language":28},"lvwangshu","2026-09-02T08:56","2026-09-02T20:37:50","https:\u002F\u002Fwww.cnblogs.com\u002FlvwangshuOI\u002Fp\u002F22801401","中文",{"format":30,"policy":31,"normalized":21,"html":32,"text":33,"wordCount":34,"hasBody":21},"HTML","NEWS_CONTENT_V1","分治：序列分治（CDQ）与点分治\n\u003Ch2>一、分治思想概述\u003C\u002Fh2>\n\u003Cp>\u003Cstrong>分治\u003C\u002Fstrong>（Divide and Conquer）是算法设计中最核心的思想之一。它的基本策略是：\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>分（Divide）\u003C\u002Fstrong>：将原问题划分为规模更小的子问题。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>治（Conquer）\u003C\u002Fstrong>：递归地求解子问题（若子问题足够小则直接求解）。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>合（Combine）\u003C\u002Fstrong>：将子问题的解合并为原问题的解。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>分治的威力在于：如果每次将问题规模缩小为原来的一半，那么递归深度仅为 \u003Cspan>\\(O(\\log n)\\)\u003C\u002Fspan>，配合高效的合并策略，往往能得到 \u003Cspan>\\(O(n \\log n)\\)\u003C\u002Fspan> 级别的算法。\u003C\u002Fp>\n\u003Cp>经典的分治算法包括：归并排序、快速排序、线段树等。而本文要讨论的 \u003Cstrong>CDQ 分治\u003C\u002Fstrong>和\u003Cstrong>点分治\u003C\u002Fstrong>，是分治思想在竞赛中的两大高级应用，分别处理\u003Cstrong>序列上的偏序 \u002F动态问题\u003C\u002Fstrong>和\u003Cstrong>树上的路径问题\u003C\u002Fstrong>。\u003C\u002Fp>\n\u003Chr>\n\u003Ch2>二、序列分治——CDQ 分治\u003C\u002Fh2>\n\u003Ch3>2.1 基本概念与核心思想\u003C\u002Fh3>\n\u003Cp>CDQ 分治是一种\u003Cstrong>基于时间（序列顺序）的分治思想\u003C\u002Fstrong>，由 IOI2008 金牌得主陈丹琦在高中时整理总结，因而得名。它不是某个具体算法，而是一种处理问题的\u003Cstrong>范式\u003C\u002Fstrong>，与动态规划类似。\u003C\u002Fp>\n\u003Cp>CDQ 分治主要应用于以下三类问题：\u003C\u002Fp>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>类别\u003C\u002Fth>\n   \u003Cth>问题描述\u003C\u002Fth>\n   \u003Cth>核心做法\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>点对统计\u003C\u002Ftd>\n   \u003Ctd>统计序列中满足某些偏序关系的点对数量\u003C\u002Ftd>\n   \u003Ctd>分治 + 数据结构合并\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>1D\u002F1D DP 优化\u003C\u002Ftd>\n   \u003Ctd>一维 DP，转移 \u003Cspan>\\(O(n)\\)\u003C\u002Fspan> 降为 \u003Cspan>\\(O(\\log n)\\)\u003C\u002Fspan>\u003C\u002Ftd>\n   \u003Ctd>分治区间转移\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>动态转静态\u003C\u002Ftd>\n   \u003Ctd>带修改的操作序列转化为离线静态问题\u003C\u002Ftd>\n   \u003Ctd>以时间为分治中点\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Ch4>核心流程\u003C\u002Fh4>\n\u003Cp>对于序列上的点对问题，CDQ 分治的 \u003Ccode>solve(l, r)\u003C\u002Fcode> 处理区间 \u003Cspan>\\([l, r]\\)\u003C\u002Fspan> 内所有点对的贡献。算法步骤为：\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>取中点\u003C\u002Fstrong> \u003Cspan>\\(mid = \\lfloor (l+r)\u002F2 \\rfloor\\)\u003C\u002Fspan>。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>划分点对\u003C\u002Fstrong>：将 \u003Cspan>\\([l, r]\\)\u003C\u002Fspan> 内的所有点对 \u003Cspan>\\((i, j)\\)\u003C\u002Fspan>（\u003Cspan>\\(i &lt; j\\)\u003C\u002Fspan>）分为三类： \n  \u003Cul>\n   \u003Cli>第一类：\u003Cspan>\\(i, j \\in [l, mid]\\)\u003C\u002Fspan>（都在左半）\u003C\u002Fli>\n   \u003Cli>第二类：\u003Cspan>\\(i \\in [l, mid],\\; j \\in [mid+1, r]\\)\u003C\u002Fspan>（跨左右）\u003C\u002Fli>\n   \u003Cli>第三类：\u003Cspan>\\(i, j \\in [mid+1, r]\\)\u003C\u002Fspan>（都在右半）\u003C\u002Fli>\n  \u003C\u002Ful>\u003C\u002Fli>\n \u003Cli>\u003Cstrong>递归处理\u003C\u002Fstrong>第一类 \u003Ccode>solve(l, mid)\u003C\u002Fcode> 和第三类 \u003Ccode>solve(mid+1, r)\u003C\u002Fcode>。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>合并处理\u003C\u002Fstrong>第二类——计算左半对右半的贡献。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cblockquote>\n \u003Cp>\u003Cstrong>关键洞察\u003C\u002Fstrong>：递归处理完左右两半后，只剩\"跨左右\"的点对需要额外处理。而此时左半的下标 \u003Cspan>\\(i \\le mid &lt; j\\)\u003C\u002Fspan>，天然保证了 \u003Cspan>\\(i &lt; j\\)\u003C\u002Fspan> 的\u003Cstrong>偏序\u003C\u002Fstrong>关系（即\"时间维\"已被分治结构处理）。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>这正是 CDQ 分治的精髓——\u003Cstrong>用分治的中点天然消除一个维度\u003C\u002Fstrong>。\u003C\u002Fp>\n\u003Ch3>2.2 经典应用：三维偏序\u003C\u002Fh3>\n\u003Cblockquote>\n \u003Cp>\u003Cstrong>题目\u003C\u002Fstrong>：\u003Ca href=\"https:\u002F\u002Fwww.luogu.com.cn\u002Fproblem\u002FP3810\" target=\"_blank\" rel=\"noopener noreferrer nofollow\">洛谷 P3810 【模板】三维偏序（陌上花开）\u003C\u002Fa>\u003C\u002Fp>\n \u003Cp>给定 \u003Cspan>\\(n\\)\u003C\u002Fspan> 个元素，每个元素有三个属性 \u003Cspan>\\((a, b, c)\\)\u003C\u002Fspan>。统计满足 \u003Cspan>\\(a_i \\le a_j,\\; b_i \\le b_j,\\; c_i \\le c_j\\)\u003C\u002Fspan> 且 \u003Cspan>\\(i &lt; j\\)\u003C\u002Fspan> 的点对数量。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Ch4>分析\u003C\u002Fh4>\n\u003Cp>三个维度需要分别处理。我们对每一维使用不同的工具：\u003C\u002Fp>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>维度\u003C\u002Fth>\n   \u003Cth>处理方式\u003C\u002Fth>\n   \u003Cth>原因\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>第一维 \u003Cspan>\\(a\\)\u003C\u002Fspan>\u003C\u002Ftd>\n   \u003Ctd>\u003Cstrong>排序\u003C\u002Fstrong>\u003C\u002Ftd>\n   \u003Ctd>全局排序保证 \u003Cspan>\\(a\\)\u003C\u002Fspan> 的偏序关系\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>第二维 \u003Cspan>\\(b\\)\u003C\u002Fspan>\u003C\u002Ftd>\n   \u003Ctd>\u003Cstrong>CDQ 分治（归并排序）\u003C\u002Fstrong>\u003C\u002Ftd>\n   \u003Ctd>分治中点消除下标偏序，归并按 \u003Cspan>\\(b\\)\u003C\u002Fspan> 排序\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>第三维 \u003Cspan>\\(c\\)\u003C\u002Fspan>\u003C\u002Ftd>\n   \u003Ctd>\u003Cstrong>树状数组\u003C\u002Fstrong>\u003C\u002Ftd>\n   \u003Ctd>在合并阶段动态查询 \u003Cspan>\\(c\\)\u003C\u002Fspan> 的前缀和\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Ch4>算法步骤\u003C\u002Fh4>\n\u003Col>\n \u003Cli>\u003Cp>\u003Cstrong>预处理\u003C\u002Fstrong>：将序列按 \u003Cspan>\\(a\\)\u003C\u002Fspan> 从小到大排序（若 \u003Cspan>\\(a\\)\u003C\u002Fspan> 相同则按 \u003Cspan>\\(b\\)\u003C\u002Fspan>，再按 \u003Cspan>\\(c\\)\u003C\u002Fspan>）。这样保证了在序列中 \u003Cspan>\\(i\\)\u003C\u002Fspan> 排在 \u003Cspan>\\(j\\)\u003C\u002Fspan> 前面时 \u003Cspan>\\(a_i \\le a_j\\)\u003C\u002Fspan>。同时，将完全相同的元素合并，记录出现次数 \u003Cspan>\\(cnt\\)\u003C\u002Fspan>。\u003C\u002Fp>\u003C\u002Fli>\n \u003Cli>\u003Cp>\u003Cstrong>CDQ 分治\u003C\u002Fstrong> \u003Ccode>solve(l, r)\u003C\u002Fcode>：\u003C\u002Fp>\n  \u003Cul>\n   \u003Cli>递归 \u003Ccode>solve(l, mid)\u003C\u002Fcode> 和 \u003Ccode>solve(mid+1, r)\u003C\u002Fcode>。\u003C\u002Fli>\n   \u003Cli>此时左半 \u003Cspan>\\([l, mid]\\)\u003C\u002Fspan> 的元素 \u003Cspan>\\(a\\)\u003C\u002Fspan> 值 \u003Cspan>\\(\\le\\)\u003C\u002Fspan> 右半 \u003Cspan>\\([mid+1, r]\\)\u003C\u002Fspan> 的元素 \u003Cspan>\\(a\\)\u003C\u002Fspan> 值（因为预先按 \u003Cspan>\\(a\\)\u003C\u002Fspan> 排序，分治不会打乱这个关系），\u003Cstrong>第一维已满足\u003C\u002Fstrong>。\u003C\u002Fli>\n   \u003Cli>将左右两半分别按 \u003Cspan>\\(b\\)\u003C\u002Fspan> 排序（用归并或直接 \u003Ccode>sort\u003C\u002Fcode>）。\u003C\u002Fli>\n   \u003Cli>用双指针：遍历右半每个元素 \u003Cspan>\\(j\\)\u003C\u002Fspan>，将左半所有 \u003Cspan>\\(b_i \\le b_j\\)\u003C\u002Fspan> 的元素 \u003Cspan>\\(i\\)\u003C\u002Fspan> 的 \u003Cspan>\\(c\\)\u003C\u002Fspan> 值加入树状数组。\u003C\u002Fli>\n   \u003Cli>查询树状数组中 \u003Cspan>\\(\\le c_j\\)\u003C\u002Fspan> 的元素个数，即为左半对 \u003Cspan>\\(j\\)\u003C\u002Fspan> 的贡献。\u003C\u002Fli>\n  \u003C\u002Ful>\u003C\u002Fli>\n \u003Cli>\u003Cp>\u003Cstrong>统计答案\u003C\u002Fstrong>：对于去重后的第 \u003Cspan>\\(i\\)\u003C\u002Fspan> 个元素，其满足条件的点对数为 \u003Cspan>\\(res_i + cnt_i - 1\\)\u003C\u002Fspan>（包括自身）。\u003C\u002Fp>\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch3>2.3 代码实现与复杂度分析\u003C\u002Fh3>\n\u003Cpre>\u003Ccode>#include &lt;bits\u002Fstdc++.h&gt;\nusing namespace std;\n\nusing ll = long long;\n\nconst int N = 1e5 + 5;\nconst int K = 2e5 + 5;\n\nll n, k;\n\nstruct node {\n    ll a, b, c;\n    ll cnt, res;\n};\nnode x[N];\nnode nx[N];\n\nll m;\nll ans[N], bit[N];\n\nbool cmpA(node g, node h) {\n    if (g.a != h.a) return g.a &lt; h.a;\n    if (g.b != h.b) return g.b &lt; h.b;\n    return g.c &lt; h.c;\n}\n\nbool cmpB(node g, node h) {\n    if (g.b != h.b) return g.b &lt; h.b;\n    return g.c &lt; h.c;\n}\n\nll lowbit(ll g) {\n    return g &amp; (-g);\n}\n\nvoid add(ll g, ll h) {\n    for (; g &lt;= k; g += lowbit(g)) {\n        bit[g] += h;\n    }\n}\n\nll query(ll g) {\n    ll ans = 0;\n    for (; g; g -= lowbit(g)) ans += bit[g];\n    return ans;\n}\n\nvoid CDQ(ll l, ll r) {\n    if (l == r) return;\n    ll mid = (l + r) \u002F 2;\n\n    CDQ(l, mid);\n    CDQ(mid + 1, r);\n\n    sort(nx + l, nx + mid + 1, cmpB);\n    sort(nx + mid + 1, nx + r + 1, cmpB);\n\n    ll i = l, j = mid + 1;\n    while (j &lt;= r) {\n        while (i &lt;= mid &amp;&amp; nx[i].b &lt;= nx[j].b) {\n            add(nx[i].c, nx[i].cnt);\n            i ++;\n        }\n        nx[j].res += query(nx[j].c);\n        j ++;\n    }\n\n    for (ll p = l; p &lt; i; p ++) add(nx[p].c, -nx[p].cnt);\n}\n\nint main () {\n\u002F\u002F\tfreopen(\"\", \"r\", stdin);\n\u002F\u002F\tfreopen(\"\", \"w\", stdout);\n\n    ios::sync_with_stdio(false);\n    cin.tie(0);\n\n    cin &gt;&gt; n &gt;&gt; k;\n    for (ll i = 1; i &lt;= n; i ++) {\n        cin &gt;&gt; x[i].a &gt;&gt; x[i].b &gt;&gt; x[i].c;\n    }\n\n    sort(x + 1, x + n + 1, cmpA);\n\n    ll t = 0;\n    for (ll i = 1; i &lt;= n; i ++) {\n        t ++;\n        if (i == n || !(x[i].a == x[i + 1].a &amp;&amp; x[i].b == x[i + 1].b &amp;&amp; x[i].c == x[i + 1].c)) {\n            nx[++ m].a = x[i].a;\n            nx[m].b = x[i].b;\n            nx[m].c = x[i].c;\n            nx[m].cnt = t;\n            t = 0;\n        }\n    }\n\n    CDQ(1, m);\n\n    for (ll i = 1; i &lt;= m; i ++) {\n        ans[nx[i].res + nx[i].cnt - 1] += nx[i].cnt;\n    }\n    for (ll i = 0; i &lt; n; i ++) {\n        cout &lt;&lt; ans[i] &lt;&lt; '\\n';\n    }\n\n    return 0;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch4>复杂度分析\u003C\u002Fh4>\n\u003Cul>\n \u003Cli>\u003Cstrong>时间复杂度\u003C\u002Fstrong>：CDQ 分治共 \u003Cspan>\\(O(\\log n)\\)\u003C\u002Fspan> 层，每层中排序和双指针扫描均为 \u003Cspan>\\(O(n \\log n)\\)\u003C\u002Fspan>（树状数组操作 \u003Cspan>\\(O(\\log k)\\)\u003C\u002Fspan>），总计 \u003Cspan>\\(O(n \\log^2 n)\\)\u003C\u002Fspan>。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>空间复杂度\u003C\u002Fstrong>：\u003Cspan>\\(O(n + k)\\)\u003C\u002Fspan>。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch4>代码细节要点\u003C\u002Fh4>\n\u003Col>\n \u003Cli>\u003Cp>\u003Cstrong>去重\u003C\u002Fstrong>：完全相同的 \u003Cspan>\\((a, b, c)\\)\u003C\u002Fspan> 元素必须合并，否则会漏算。去重后记录 \u003Cspan>\\(cnt\\)\u003C\u002Fspan>，最终答案为 \u003Cspan>\\(res_i + cnt_i - 1\\)\u003C\u002Fspan>。\u003C\u002Fp>\u003C\u002Fli>\n \u003Cli>\u003Cp>\u003Cstrong>清空树状数组\u003C\u002Fstrong>：不能用 \u003Ccode>memset\u003C\u002Fcode>（会超时），只能将用过的位置逐个减回去。这是保证复杂度的关键。\u003C\u002Fp>\u003C\u002Fli>\n \u003Cli>\u003Cp>\u003Cstrong>排序的等价性\u003C\u002Fstrong>：在合并阶段对左右两半分别按 \u003Cspan>\\(b\\)\u003C\u002Fspan> 排序，不会破坏第一维 \u003Cspan>\\(a\\)\u003C\u002Fspan> 的大小关系（因为左半的 \u003Cspan>\\(a\\)\u003C\u002Fspan> 普遍 \u003Cspan>\\(\\le\\)\u003C\u002Fspan> 右半），这是 CDQ 分治正确性的基础。\u003C\u002Fp>\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch3>2.4 CDQ 套 CDQ：四维偏序\u003C\u002Fh3>\n\u003Cblockquote>\n \u003Cp>四维偏序：给定 \u003Cspan>\\(n\\)\u003C\u002Fspan> 个四元组 \u003Cspan>\\((a, b, c, d)\\)\u003C\u002Fspan>，统计满足四维均 \u003Cspan>\\(\\le\\)\u003C\u002Fspan> 的点对数。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>处理思路与三维偏序类似，逐维降低：\u003C\u002Fp>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>维度\u003C\u002Fth>\n   \u003Cth>处理方式\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>第一维 \u003Cspan>\\(a\\)\u003C\u002Fspan>\u003C\u002Ftd>\n   \u003Ctd>排序\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>第二维 \u003Cspan>\\(b\\)\u003C\u002Fspan>\u003C\u002Ftd>\n   \u003Ctd>外层 CDQ 分治\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>第三维 \u003Cspan>\\(c\\)\u003C\u002Fspan>\u003C\u002Ftd>\n   \u003Ctd>内层 CDQ 分治（或归并排序）\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>第四维 \u003Cspan>\\(d\\)\u003C\u002Fspan>\u003C\u002Ftd>\n   \u003Ctd>树状数组\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Cp>\u003Cstrong>外层 CDQ\u003C\u002Fstrong> 负责按 \u003Cspan>\\(b\\)\u003C\u002Fspan> 的偏序关系分治，同时给左半元素打标记 \u003Ccode>tag=1\u003C\u002Fcode>，右半元素 \u003Ccode>tag=0\u003C\u002Fcode>。合并时按 \u003Cspan>\\(b\\)\u003C\u002Fspan> 排序后，将所有元素送入内层。\u003C\u002Fp>\n\u003Cp>\u003Cstrong>内层 CDQ\u003C\u002Fstrong> 对 \u003Ccode>tag\u003C\u002Fcode> 数组按 \u003Cspan>\\(c\\)\u003C\u002Fspan> 分治归并，合并时用双指针按 \u003Cspan>\\(c\\)\u003C\u002Fspan> 排序，将 \u003Ccode>tag=1\u003C\u002Fcode> 的元素加入树状数组，\u003Ccode>tag=0\u003C\u002Fcode> 的元素查询。\u003C\u002Fp>\n\u003Cpre>\u003Ccode>\u002F\u002F 外层 CDQ：处理 b 维\nvoid cdq2(int l, int r) {\n    if (l == r) return;\n    int mid = (l + r) &gt;&gt; 1;\n    cdq2(l, mid);\n    cdq2(mid + 1, r);\n\n    \u002F\u002F 给左半打标记\n    for (int i = l; i &lt;= mid; i++) tmp[i].tag = 1;\n    for (int i = mid + 1; i &lt;= r; i++) tmp[i].tag = 0;\n\n    \u002F\u002F 按 b 归并，送入内层 CDQ 处理 c, d 两维\n    merge_by_b(l, mid, mid + 1, r);  \u002F\u002F 归并到 tmp2[l..r]\n    cdq_inner(l, r);                  \u002F\u002F 内层处理 c, d\n\n    \u002F\u002F 恢复\n    for (int i = l; i &lt;= r; i++) \u002F* restore order *\u002F;\n}\n\n\u002F\u002F 内层 CDQ：处理 c 维，用树状数组处理 d 维\nvoid cdq_inner(int l, int r) {\n    if (l == r) return;\n    int mid = (l + r) &gt;&gt; 1;\n    cdq_inner(l, mid);\n    cdq_inner(mid + 1, r);\n\n    sort(tmp2 + l, tmp2 + mid + 1, cmpC);  \u002F\u002F 按 c 排序\n    sort(tmp2 + mid + 1, tmp2 + r + 1, cmpC);\n\n    int i = l, j = mid + 1;\n    while (j &lt;= r) {\n        while (i &lt;= mid &amp;&amp; tmp2[i].c &lt;= tmp2[j].c) {\n            if (tmp2[i].tag) bit.add(tmp2[i].d, 1);\n            i++;\n        }\n        if (!tmp2[j].tag)\n            tmp2[j].ans += bit.query(tmp2[j].d);\n        j++;\n    }\n    for (int p = l; p &lt; i; p++)\n        if (tmp2[p].tag) bit.add(tmp2[p].d, -1);\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>复杂度\u003C\u002Fstrong>：\u003Cspan>\\(O(n \\log^3 n)\\)\u003C\u002Fspan>。实际上常数较大，\u003Cspan>\\(n\\)\u003C\u002Fspan> 不太大时可用。\u003C\u002Fp>\n\u003Cblockquote>\n \u003Cp>\u003Cstrong>规律总结\u003C\u002Fstrong>：\u003Cspan>\\(k\\)\u003C\u002Fspan> 维偏序可以用 \u003Cspan>\\(k-2\\)\u003C\u002Fspan> 层 CDQ 套 CDQ + 1 次排序 + 1 个树状数组解决，复杂度 \u003Cspan>\\(O(n \\log^k n)\\)\u003C\u002Fspan>。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Ch3>2.5 动态问题转静态\u003C\u002Fh3>\n\u003Cp>CDQ 分治还有一个强大应用：将\u003Cstrong>带修改的动态问题\u003C\u002Fstrong>转化为\u003Cstrong>离线静态问题\u003C\u002Fstrong>。\u003C\u002Fp>\n\u003Cblockquote>\n \u003Cp>\u003Cstrong>典型例题\u003C\u002Fstrong>：\u003Ca href=\"https:\u002F\u002Fwww.luogu.com.cn\u002Fproblem\u002FP3157\" target=\"_blank\" rel=\"noopener noreferrer nofollow\">CQOI2011 动态逆序对\u003C\u002Fa>\u003C\u002Fp>\n \u003Cp>给定 \u003Cspan>\\(1 \\sim n\\)\u003C\u002Fspan> 的排列，按给定顺序依次删除 \u003Cspan>\\(m\\)\u003C\u002Fspan> 个元素，每次删除前求当前逆序对数。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>\u003Cstrong>分析\u003C\u002Fstrong>：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>每个元素有两个属性：值 \u003Cspan>\\(val\\)\u003C\u002Fspan> 和删除时间 \u003Cspan>\\(del\\)\u003C\u002Fspan>（未删除的设为 \u003Cspan>\\(m+1\\)\u003C\u002Fspan>）。\u003C\u002Fli>\n \u003Cli>对于元素 \u003Cspan>\\(i\\)\u003C\u002Fspan>，删除它时损失的逆序对数 = 在它之前删除且值比它大的元素数 + 在它之后删除且值比它小的元素数。\u003C\u002Fli>\n \u003Cli>即统计满足 \u003Cspan>\\(del_j &lt; del_i\\)\u003C\u002Fspan> 且 \u003Cspan>\\(val_j &gt; val_i\\)\u003C\u002Fspan> 的点对——这又是一个三维偏序问题！\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>\u003Cstrong>三维为\u003C\u002Fstrong>：时间维（删除顺序 \u003Cspan>\\(del\\)\u003C\u002Fspan>）、值维 \u003Cspan>\\(val\\)\u003C\u002Fspan>、贡献方向。\u003C\u002Fp>\n\u003Cp>用 CDQ 分治处理时间维，归并处理 \u003Cspan>\\(val\\)\u003C\u002Fspan> 维，树状数组统计贡献。CDQ 分治的\"动态转静态\"能力，使得许多在线问题可以离线高效解决。\u003C\u002Fp>\n\u003Chr>\n\u003Ch2>三、点分治\u003C\u002Fh2>\n\u003Ch3>3.1 树的重心\u003C\u002Fh3>\n\u003Cp>点分治的核心在于选择一个合适的\"分治中心\"——\u003Cstrong>树的重心\u003C\u002Fstrong>。\u003C\u002Fp>\n\u003Cblockquote>\n \u003Cp>\u003Cstrong>定义\u003C\u002Fstrong>：对于树中的某个节点 \u003Cspan>\\(u\\)\u003C\u002Fspan>，删除 \u003Cspan>\\(u\\)\u003C\u002Fspan> 后树分裂为若干子树。记其中最大子树的大小为 \u003Cspan>\\(max\\_subtree(u)\\)\u003C\u002Fspan>。使 \u003Cspan>\\(max\\_subtree(u)\\)\u003C\u002Fspan> 最小的节点 \u003Cspan>\\(u\\)\u003C\u002Fspan> 称为树的\u003Cstrong>重心（centroid）\u003C\u002Fstrong>。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Ch4>重要性质\u003C\u002Fh4>\n\u003Col>\n \u003Cli>\u003Cp>\u003Cstrong>子树大小不超过一半\u003C\u002Fstrong>：以重心为根时，所有子树的大小不超过 \u003Cspan>\\(n\u002F2\\)\u003C\u002Fspan>。这是保证分治深度为 \u003Cspan>\\(O(\\log n)\\)\u003C\u002Fspan> 的关键。\u003C\u002Fp>\u003C\u002Fli>\n \u003Cli>\u003Cp>\u003Cstrong>重心的唯一性\u003C\u002Fstrong>：一棵树至少有 1 个重心，至多有 2 个重心。若有两个重心，则它们相邻，且树有偶数个节点。\u003C\u002Fp>\u003C\u002Fli>\n \u003Cli>\u003Cp>\u003Cstrong>距离和最小\u003C\u002Fstrong>：树中所有点到某个点的距离和，以重心处最小。\u003C\u002Fp>\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch4>求重心的方法\u003C\u002Fh4>\n\u003Cp>两次 DFS ：\u003C\u002Fp>\n\u003Cpre>\u003Ccode>int siz[N], maxS[N], root, maxPart;\n\n\u002F\u002F 第一次 DFS：计算子树大小\nvoid dfs1(int u, int f) {\n    siz[u] = 1;\n    for (int i = head[u]; i; i = nxt[i]) {\n        int v = to[i];\n        if (v == f || vis[v]) continue;\n        dfs1(v, u);\n        siz[u] += siz[v];\n    }\n}\n\n\u002F\u002F 第二次 DFS：计算最大子树大小并找重心\nvoid dfs2(int u, int f, int total) {\n    maxS[u] = total - siz[u];          \u002F\u002F 向上的子树\n    for (int i = head[u]; i; i = nxt[i]) {\n        int v = to[i];\n        if (v == f || vis[v]) continue;\n        maxS[u] = max(maxS[u], siz[v]);\n        dfs2(v, u, total);\n    }\n    if (maxS[u] &lt; maxPart) {\n        maxPart = maxS[u];\n        root = u;\n    }\n}\n\n\u002F\u002F 使用示例（以 1 为根，整棵树大小 n）\nmaxPart = INF;\ndfs1(1, 0);\ndfs2(1, 0, n);\n\u002F\u002F root 即为重心\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>3.2 基本思想与算法流程\u003C\u002Fh3>\n\u003Cp>点分治适合处理大规模的\u003Cstrong>树上路径信息\u003C\u002Fstrong>问题。\u003C\u002Fp>\n\u003Ch4>核心思想\u003C\u002Fh4>\n\u003Cp>树上任意一条路径，必然经过某个\"中心节点\"。我们将所有路径分为两类：\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>经过当前根节点\u003C\u002Fstrong>的路径。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>不经过当前根节点\u003C\u002Fstrong>的路径（完全在某个子树内部）。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>对于第一类路径，可以在当前层直接统计；对于第二类路径，递归到子树中处理。\u003C\u002Fp>\n\u003Ch4>算法流程\u003C\u002Fh4>\n\u003Cpre>\u003Ccode>divide(root):\n    1. 找到当前连通块的重心 root\n    2. 统计所有经过 root 的路径的贡献\n    3. 标记 root 为已处理（vis[root] = true）\n    4. 对 root 的每个子节点所在连通块，递归执行 divide\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch4>路径分类细化\u003C\u002Fh4>\n\u003Cp>对于经过根节点 \u003Cspan>\\(rt\\)\u003C\u002Fspan> 的路径，进一步细分：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>以 \u003Cspan>\\(rt\\)\u003C\u002Fspan> 为一个端点\u003C\u002Fstrong>的路径：只需统计 \u003Cspan>\\(rt\\)\u003C\u002Fspan> 到子树中每个节点的距离信息。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>两个端点都不为\u003C\u002Fstrong>&#xa0;\u003Cspan>\\(rt\\)\u003C\u002Fspan> 的路径：可以由两条\"以 \u003Cspan>\\(rt\\)\u003C\u002Fspan> 为端点\"的路径合并得到（路径 \u003Cspan>\\(u \\to v\\)\u003C\u002Fspan> = 路径 \u003Cspan>\\(u \\to rt\\)\u003C\u002Fspan> + 路径 \u003Cspan>\\(rt \\to v\\)\u003C\u002Fspan>）。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>因此处理过程为：\u003C\u002Fp>\n\u003Col>\n \u003Cli>遍历 \u003Cspan>\\(rt\\)\u003C\u002Fspan> 的每个子节点 \u003Cspan>\\(ch\\)\u003C\u002Fspan>，计算 \u003Cspan>\\(ch\\)\u003C\u002Fspan> 子树中所有节点到 \u003Cspan>\\(rt\\)\u003C\u002Fspan> 的距离。\u003C\u002Fli>\n \u003Cli>对于每条新计算的路径，检查能否与已处理的子树中路径拼接成答案。\u003C\u002Fli>\n \u003Cli>将新路径信息加入总信息中。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cblockquote>\n \u003Cp>\u003Cstrong>注意\u003C\u002Fstrong>：清空信息时不能直接 \u003Ccode>memset\u003C\u002Fcode>，应记录用过的位置然后逐个清空，保证复杂度正确。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Ch3>3.3 经典应用：树上距离为 k 的路径\u003C\u002Fh3>\n\u003Cblockquote>\n \u003Cp>\u003Cstrong>题目\u003C\u002Fstrong>：\u003Ca href=\"https:\u002F\u002Fwww.luogu.com.cn\u002Fproblem\u002FP3806\" target=\"_blank\" rel=\"noopener noreferrer nofollow\">洛谷 P3806【模板】点分治\u003C\u002Fa>\u003C\u002Fp>\n \u003Cp>给定一棵 \u003Cspan>\\(n\\)\u003C\u002Fspan> 个节点的带边权树，\u003Cspan>\\(m\\)\u003C\u002Fspan> 次询问，每次给出 \u003Cspan>\\(k\\)\u003C\u002Fspan>，问树上是否存在距离为 \u003Cspan>\\(k\\)\u003C\u002Fspan> 的点对。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Ch4>分析\u003C\u002Fh4>\n\u003Cul>\n \u003Cli>对于每个询问 \u003Cspan>\\(k\\)\u003C\u002Fspan>，检查是否存在两点距离恰为 \u003Cspan>\\(k\\)\u003C\u002Fspan>。\u003C\u002Fli>\n \u003Cli>使用点分治：对每个分治层，统计经过当前重心的路径。\u003C\u002Fli>\n \u003Cli>用桶 \u003Ccode>tf[d]\u003C\u002Fcode> 记录之前处理过的子树中是否存在距离重心为 \u003Cspan>\\(d\\)\u003C\u002Fspan> 的节点。\u003C\u002Fli>\n \u003Cli>对当前子树中的每个节点 \u003Cspan>\\(i\\)\u003C\u002Fspan>（到重心距离为 \u003Cspan>\\(dist_i\\)\u003C\u002Fspan>），检查 \u003Cspan>\\(tf[k - dist_i]\\)\u003C\u002Fspan> 是否为 \u003Ccode>true\u003C\u002Fcode>。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch4>处理顺序（关键！）\u003C\u002Fh4>\n\u003Cpre>\u003Ccode>对于重心的每个子树 ch:\n    1. 先计算 ch 子树中所有节点到重心的距离\n    2. 检查这些路径能否与\"之前处理过的子树\"中的路径组合成答案\n    3. 将这些路径信息加入桶中\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cblockquote>\n \u003Cp>为什么要\"先查询再加入\"？因为这样可以避免同一子树内的两个节点被错误地配对（它们的路径不经过重心，而是经过子树内的某个节点，会在递归处理该子树时被统计到）。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Ch3>3.4 AC code\u003C\u002Fh3>\n\u003Cpre>\u003Ccode>#include &lt;bits\u002Fstdc++.h&gt;\nusing namespace std;\n\nconst int N = 1e4 + 5;\nconst int M = 105;\nconst int Q = 1e7 + 7;\n\nint n, m;\nint head[N], ver[N * 2], nxt[N * 2], edg[N * 2], tot;\n\nvoid add(int u, int v, int w) {\n    ver[++tot] = v;\n    nxt[tot] = head[u];\n    head[u] = tot;\n    edg[tot] = w;\n}\n\nint siz[N], maxS[N], root, maxp;\nbool vis[N];\n\nvoid dfs1(int u, int fa) {\n    siz[u] = 1;\n    for (int i = head[u]; i; i = nxt[i]) {\n        int v = ver[i];\n        if (v == fa || vis[v]) continue;\n        \n        dfs1(v, u);\n        siz[u] += siz[v];\n    }\n}\n\nvoid dfs2(int u, int fa, int total) {\n    maxS[u] = total - siz[u];\n    for (int i = head[u]; i; i = nxt[i]) {\n        int v = ver[i];\n        if (v == fa || vis[v]) continue;\n        \n        maxS[u] = max(maxS[u], siz[v]);\n        dfs2(v, u, total);\n    }\n    if (maxS[u] &lt; maxp) {\n        maxp = maxS[u];\n        root = u;\n    }\n}\n\nint dist[N], cnt;\nint ask[M];\nbool ans[M];\nbool bucket[Q];\nvector&lt;int&gt; used;\n\nvoid dis(int u, int fa, int d) {\n    dist[++ cnt] = d;\n    for (int i = head[u]; i; i = nxt[i]) {\n        int v = ver[i];\n        if (v == fa || vis[v]) continue;\n        \n        dis(v, u, d + edg[i]);\n    }\n}\n\nvoid divide(int u) {\n    dfs1(u, 0);                 \n    int total = siz[u];\n    maxp = INT_MAX;\n    dfs2(u, 0, total);          \n    u = root;                   \n\n    vis[u] = true;\n    bucket[0] = true;\n    used.push_back(0);\n\n    for (int i = head[u]; i; i = nxt[i]) {\n        int v = ver[i];\n        if (vis[v]) continue;\n\n        cnt = 0;\n        dis(v, u, edg[i]);\n\n        for (int j = 1; j &lt;= cnt; j ++) {\n            for (int q = 0; q &lt; m; q ++) {\n                if (ans[q]) continue;\n                if (ask[q] &gt;= dist[j] &amp;&amp; bucket[ask[q] - dist[j]]) {\n                    ans[q] = true;\n                }\n            }\n        }\n\n        for (int j = 1; j &lt;= cnt; j ++) {\n            if (dist[j] &lt; Q) {\n                bucket[dist[j]] = true;\n                used.push_back(dist[j]);\n            }\n        }\n    }\n\n    for (int d : used) {\n        if (d &lt; Q) bucket[d] = false;\n    }\n    used.clear();\n\n    for (int i = head[u]; i; i = nxt[i]) {\n        int v = ver[i];\n        if (vis[v]) continue;\n        \n        divide(v);\n    }\n}\n\nint main() {\n    ios::sync_with_stdio(false);\n    cin.tie(0);\n\n    cin &gt;&gt; n &gt;&gt; m;\n    for (int i = 1; i &lt; n; i ++) {\n        int u, v, w; cin &gt;&gt; u &gt;&gt; v &gt;&gt; w;\n        \n        add(u, v, w);\n        add(v, u, w);\n    }\n    for (int i = 0; i &lt; m; i ++) cin &gt;&gt; ask[i];\n\n    divide(1);\n\n    for (int i = 0; i &lt; m; i ++) {\n        if (ans[i]) cout &lt;&lt; \"AYE\\n\";\n        else cout &lt;&lt; \"NAY\\n\";\n    }\n\n    return 0;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch4>复杂度分析\u003C\u002Fh4>\n\u003Cul>\n \u003Cli>\u003Cstrong>找重心\u003C\u002Fstrong>：每层 \u003Cspan>\\(O(n)\\)\u003C\u002Fspan>（DFS 遍历整个连通块）。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>统计路径\u003C\u002Fstrong>：每层 \u003Cspan>\\(O(n)\\)\u003C\u002Fspan>（遍历所有节点计算距离 + 查询）。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>递归深度\u003C\u002Fstrong>：由于每次选重心，子树大小不超过 \u003Cspan>\\(n\u002F2\\)\u003C\u002Fspan>，递归深度为 \u003Cspan>\\(O(\\log n)\\)\u003C\u002Fspan>。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>总复杂度\u003C\u002Fstrong>：\u003Cspan>\\(O(n \\log n)\\)\u003C\u002Fspan>（对于本题路径存在性查询），或 \u003Cspan>\\(O(n \\log^2 n)\\)\u003C\u002Fspan>（若需要排序或使用数据结构）。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cblockquote>\n \u003Cp>\u003Cstrong>关键点\u003C\u002Fstrong>：选择重心保证了递归深度为 \u003Cspan>\\(O(\\log n)\\)\u003C\u002Fspan>，这是点分治效率的保障。如果随意选根（如总是选 1 号节点），在最坏情况（链状树）下递归深度退化为 \u003Cspan>\\(O(n)\\)\u003C\u002Fspan>，总复杂度退化为 \u003Cspan>\\(O(n^2)\\)\u003C\u002Fspan>。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Ch4>代码细节要点\u003C\u002Fh4>\n\u003Col>\n \u003Cli>\u003Cp>\u003Cstrong>两次 DFS 找重心\u003C\u002Fstrong>：第一次计算 \u003Ccode>total\u003C\u002Fcode>（当前连通块大小），第二次根据 \u003Ccode>total\u003C\u002Fcode> 找重心。注意每次递归到子树时，必须\u003Cstrong>重新计算子树大小\u003C\u002Fstrong>作为新的 \u003Ccode>total\u003C\u002Fcode>。\u003C\u002Fp>\u003C\u002Fli>\n \u003Cli>\u003Cp>\u003Cstrong>清空桶的方式\u003C\u002Fstrong>：不能使用 \u003Ccode>memset\u003C\u002Fcode>（会导致 \u003Cspan>\\(O(\\text{桶大小})\\)\u003C\u002Fspan> 的清空开销）。正确做法是记录所有修改过的桶位置，然后逐个清零。\u003C\u002Fp>\u003C\u002Fli>\n \u003Cli>\u003Cp>\u003Cstrong>查询先于加入\u003C\u002Fstrong>：处理每个子树时，先查询该子树路径能否与之前子树的路径组合成答案，再将该子树路径加入桶。这保证了统计的路径一定经过重心。\u003C\u002Fp>\u003C\u002Fli>\n \u003Cli>\u003Cp>\u003Cstrong>\u003Ccode>vis\u003C\u002Fcode> 数组\u003C\u002Fstrong>：标记已处理的重心节点，确保递归时不会回到上层。\u003C\u002Fp>\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch3>3.5 扩展：点分治统计距离不超过 k 的点对数\u003C\u002Fh3>\n\u003Cblockquote>\n \u003Cp>\u003Cstrong>题目\u003C\u002Fstrong>：\u003Ca href=\"https:\u002F\u002Fwww.luogu.com.cn\u002Fproblem\u002FP4178\" target=\"_blank\" rel=\"noopener noreferrer nofollow\">洛谷 P4178 Tree\u003C\u002Fa>\u003C\u002Fp>\n \u003Cp>给定带权树，求距离 \u003Cspan>\\(\\le k\\)\u003C\u002Fspan> 的点对数量。\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Cp>这与上面的\"存在性查询\"不同，需要\u003Cstrong>计数\u003C\u002Fstrong>。处理方式：\u003C\u002Fp>\n\u003Col>\n \u003Cli>对每个子树，收集所有节点到重心的距离。\u003C\u002Fli>\n \u003Cli>对距离数组排序，用双指针统计满足 \u003Cspan>\\(dist_i + dist_j \\le k\\)\u003C\u002Fspan> 的对数。\u003C\u002Fli>\n \u003Cli>减去同一子树内的重复计数（容斥）。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cpre>\u003Ccode>int countPairs(int dist[], int top, int k) {\n    sort(dist + 1, dist + top + 1);\n    int cnt = 0, r = top;\n    for (int l = 1; l &lt; r; ) {\n        if (dist[l] + dist[r] &lt;= k) {\n            cnt += r - l;\n            l++;\n        } else {\n            r--;\n        }\n    }\n    return cnt;\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>复杂度：排序 \u003Cspan>\\(O(n \\log n)\\)\u003C\u002Fspan>，每层 \u003Cspan>\\(O(n \\log n)\\)\u003C\u002Fspan>，总计 \u003Cspan>\\(O(n \\log^2 n)\\)\u003C\u002Fspan>。\u003C\u002Fp>\n\u003Chr>\n\u003Ch2>四、对比与总结\u003C\u002Fh2>\n\u003Ch3>CDQ 分治 vs 点分治\u003C\u002Fh3>\n\u003Ctable>\n \u003Cthead>\n  \u003Ctr>\n   \u003Cth>对比项\u003C\u002Fth>\n   \u003Cth>CDQ 分治（序列分治）\u003C\u002Fth>\n   \u003Cth>点分治（树分治）\u003C\u002Fth>\n  \u003C\u002Ftr>\n \u003C\u002Fthead>\n \u003Ctbody>\n  \u003Ctr>\n   \u003Ctd>\u003Cstrong>适用场景\u003C\u002Fstrong>\u003C\u002Ftd>\n   \u003Ctd>序列上的偏序问题、DP 优化、动态转静态\u003C\u002Ftd>\n   \u003Ctd>树上路径统计\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>\u003Cstrong>分治对象\u003C\u002Fstrong>\u003C\u002Ftd>\n   \u003Ctd>序列的下标区间 \u003Cspan>\\([l, r]\\)\u003C\u002Fspan>\u003C\u002Ftd>\n   \u003Ctd>树的连通块（以重心为根）\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>\u003Cstrong>消除的维度\u003C\u002Fstrong>\u003C\u002Ftd>\n   \u003Ctd>时间维（下标顺序 \u003Cspan>\\(i &lt; j\\)\u003C\u002Fspan>）\u003C\u002Ftd>\n   \u003Ctd>空间维（路径必经过重心）\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>\u003Cstrong>分治中心\u003C\u002Fstrong>\u003C\u002Ftd>\n   \u003Ctd>区间中点 \u003Cspan>\\(mid\\)\u003C\u002Fspan>\u003C\u002Ftd>\n   \u003Ctd>树的重心\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>\u003Cstrong>保证深度的机制\u003C\u002Fstrong>\u003C\u002Ftd>\n   \u003Ctd>区间自然二分\u003C\u002Ftd>\n   \u003Ctd>重心保证子树 \u003Cspan>\\(\\le n\u002F2\\)\u003C\u002Fspan>\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>\u003Cstrong>合并方式\u003C\u002Fstrong>\u003C\u002Ftd>\n   \u003Ctd>排序 + 双指针 + 数据结构\u003C\u002Ftd>\n   \u003Ctd>桶 \u002F 排序 + 双指针 + 数据结构\u003C\u002Ftd>\n  \u003C\u002Ftr>\n  \u003Ctr>\n   \u003Ctd>\u003Cstrong>典型复杂度\u003C\u002Fstrong>\u003C\u002Ftd>\n   \u003Ctd>\u003Cspan>\\(O(n \\log^2 n)\\)\u003C\u002Fspan>\u003C\u002Ftd>\n   \u003Ctd>\u003Cspan>\\(O(n \\log n)\\)\u003C\u002Fspan> 或 \u003Cspan>\\(O(n \\log^2 n)\\)\u003C\u002Fspan>\u003C\u002Ftd>\n  \u003C\u002Ftr>\n \u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Ch3>共同核心思想\u003C\u002Fh3>\n\u003Cp>两者的本质都是\u003Cstrong>通过分治的结构天然消除一个维度\u003C\u002Fstrong>：\u003C\u002Fp>\n\u003Cul>\n \u003Cli>\u003Cstrong>CDQ 分治\u003C\u002Fstrong>：分治中点 \u003Cspan>\\(mid\\)\u003C\u002Fspan> 保证了左半下标 \u003Cspan>\\(&lt;\\)\u003C\u002Fspan> 右半下标，天然满足 \u003Cspan>\\(i &lt; j\\)\u003C\u002Fspan> 的偏序。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>点分治\u003C\u002Fstrong>：选择重心作为根，所有经过重心的路径可以拆分为两条子路径，天然保证路径经过当前层处理的节点。\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch3>分治三步法总结\u003C\u002Fh3>\n\u003Cp>无论是 CDQ 分治还是点分治，都遵循分治的三步范式：\u003C\u002Fp>\n\u003Col>\n \u003Cli>\u003Cstrong>分\u003C\u002Fstrong>：将问题划分为独立子问题（CDQ 按区间中点分；点分治按重心分子树）。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>治\u003C\u002Fstrong>：递归处理子问题（处理左\u002F右区间；处理各子树）。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>合\u003C\u002Fstrong>：合并子问题解（处理跨区间点对；处理经过重心的路径）。\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch3>学习建议\u003C\u002Fh3>\n\u003Col>\n \u003Cli>\u003Cstrong>先掌握基础\u003C\u002Fstrong>：归并排序、树状数组、树的 DFS 是前置知识，务必扎实。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>理解\"消除维度\"\u003C\u002Fstrong>：这是 CDQ 分治和点分治最核心的直觉——分治结构本身就是一个维度被处理掉了。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>注意清空方式\u003C\u002Fstrong>：无论是 CDQ 的树状数组还是点分治的桶，都不能用 \u003Ccode>memset\u003C\u002Fcode> 暴力清空，要记录修改过的位置逐个还原。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>注意处理顺序\u003C\u002Fstrong>：CDQ 中\"先递归再合并\"，点分治中\"先查询再加入桶\"，顺序的正确性是算法正确的基础。\u003C\u002Fli>\n \u003Cli>\u003Cstrong>多写模板题\u003C\u002Fstrong>：三维偏序（P3810）和点分治模板（P3806）是入门必刷题，务必独立 AC。\u003C\u002Fli>\n\u003C\u002Fol>","分治：序列分治（CDQ）与点分治 一、分治思想概述 分治（Divide and Conquer）是算法设计中最核心的思想之一。它的基本策略是： 分（Divide）：将原问题划分为规模更小的子问题。 治（Conquer）：递归地求解子问题（若子问题足够小则直接求解）。 合（Combine）：将子问题的解合并为原问题的解。 分治的威力在于：如果每次将问题规模缩小为原来的一半，那么递归深度仅为 \\(O(\\log n)\\)，配合高效的合并策略，往往能得到 \\(O(n \\log n)\\) 级别的算法。 经典的分治算法包括：归并排序、快速排序、线段树等。而本文要讨论的 CDQ 分治和点分治，是分治思想在竞赛中的两大高级应用，分别处理序列上的偏序 \u002F动态问题和树上的路径问题。 二、序列分治——CDQ 分治 2.1 基本概念与核心思想 CDQ 分治是一种基于时间（序列顺序）的分治思想，由 IOI2008 金牌得主陈丹琦在高中时整理总结，因而得名。它不是某个具体算法，而是一种处理问题的范式，与动态规划类似。 CDQ 分治主要应用于以下三类问题： 类别 问题描述 核心做法 点对统计 统计序列中满足某些偏序关系的点对数量 分治 + 数据结构合并 1D\u002F1D DP 优化 一维 DP，转移 \\(O(n)\\) 降为 \\(O(\\log n)\\) 分治区间转移 动态转静态 带修改的操作序列转化为离线静态问题 以时间为分治中点 核心流程 对于序列上的点对问题，CDQ 分治的 solve(l, r) 处理区间 \\([l, r]\\) 内所有点对的贡献。算法步骤为： 取中点 \\(mid = \\lfloor (l+r)\u002F2 \\rfloor\\)。 划分点对：将 \\([l, r]\\) 内的所有点对 \\((i, j)\\)（\\(i \u003C j\\)）分为三类： 第一类：\\(i, j \\in [l, mid]\\)（都在左半） 第二类：\\(i \\in [l, mid],\\; j \\in [mid+1, r]\\)（跨左右） 第三类：\\(i, j \\in [mid+1, r]\\)（都在右半） 递归处理第一类 solve(l, mid) 和第三类 solve(mid+1, r)。 合并处理第二类——计算左半对右半的贡献。 关键洞察：递归处理完左右两半后，只剩\"跨左右\"的点对需要额外处理。而此时左半的下标 \\(i \\le mid \u003C j\\)，天然保证了 \\(i \u003C j\\) 的偏序关系（即\"时间维\"已被分治结构处理）。 这正是 CDQ 分治的精髓——用分治的中点天然消除一个维度。 2.2 经典应用：三维偏序 题目：洛谷 P3810 【模板】三维偏序（陌上花开） 给定 \\(n\\) 个元素，每个元素有三个属性 \\((a, b, c)\\)。统计满足 \\(a_i \\le a_j,\\; b_i \\le b_j,\\; c_i \\le c_j\\) 且 \\(i \u003C j\\) 的点对数量。 分析 三个维度需要分别处理。我们对每一维使用不同的工具： 维度 处理方式 原因 第一维 \\(a\\) 排序 全局排序保证 \\(a\\) 的偏序关系 第二维 \\(b\\) CDQ 分治（归并排序） 分治中点消除下标偏序，归并按 \\(b\\) 排序 第三维 \\(c\\) 树状数组 在合并阶段动态查询 \\(c\\) 的前缀和 算法步骤 预处理：将序列按 \\(a\\) 从小到大排序（若 \\(a\\) 相同则按 \\(b\\)，再按 \\(c\\)）。这样保证了在序列中 \\(i\\) 排在 \\(j\\) 前面时 \\(a_i \\le a_j\\)。同时，将完全相同的元素合并，记录出现次数 \\(cnt\\)。 CDQ 分治 solve(l, r)： 递归 solve(l, mid) 和 solve(mid+1, r)。 此时左半 \\([l, mid]\\) 的元素 \\(a\\) 值 \\(\\le\\) 右半 \\([mid+1, r]\\) 的元素 \\(a\\) 值（因为预先按 \\(a\\) 排序，分治不会打乱这个关系），第一维已满足。 将左右两半分别按 \\(b\\) 排序（用归并或直接 sort）。 用双指针：遍历右半每个元素 \\(j\\)，将左半所有 \\(b_i \\le b_j\\) 的元素 \\(i\\) 的 \\(c\\) 值加入树状数组。 查询树状数组中 \\(\\le c_j\\) 的元素个数，即为左半对 \\(j\\) 的贡献。 统计答案：对于去重后的第 \\(i\\) 个元素，其满足条件的点对数为 \\(res_i + cnt_i - 1\\)（包括自身）。 2.3 代码实现与复杂度分析 #include \u003Cbits\u002Fstdc++.h> using namespace std; using ll = long long; const int N = 1e5 + 5; const int K = 2e5 + 5; ll n, k; struct node { ll a, b, c; ll cnt, res; }; node x[N]; node nx[N]; ll m; ll ans[N], bit[N]; bool cmpA(node g, node h) { if (g.a != h.a) return g.a \u003C h.a; if (g.b != h.b) return g.b \u003C h.b; return g.c \u003C h.c; } bool cmpB(node g, node h) { if (g.b != h.b) return g.b \u003C h.b; return g.c \u003C h.c; } ll lowbit(ll g) { return g & (-g); } void add(ll g, ll h) { for (; g \u003C= k; g += lowbit(g)) { bit[g] += h; } } ll query(ll g) { ll ans = 0; for (; g; g -= lowbit(g)) ans += bit[g]; return ans; } void CDQ(ll l, ll r) { if (l == r) return; ll mid = (l + r) \u002F 2; CDQ(l, mid); CDQ(mid + 1, r); sort(nx + l, nx + mid + 1, cmpB); sort(nx + mid + 1, nx + r + 1, cmpB); ll i = l, j = mid + 1; while (j \u003C= r) { while (i \u003C= mid && nx[i].b \u003C= nx[j].b) { add(nx[i].c, nx[i].cnt); i ++; } nx[j].res += query(nx[j].c); j ++; } for (ll p = l; p \u003C i; p ++) add(nx[p].c, -nx[p].cnt); } int main () { \u002F\u002F freopen(\"\", \"r\", stdin); \u002F\u002F freopen(\"\", \"w\", stdout); ios::sync_with_stdio(false); cin.tie(0); cin >> n >> k; for (ll i = 1; i \u003C= n; i ++) { cin >> x[i].a >> x[i].b >> x[i].c; } sort(x + 1, x + n + 1, cmpA); ll t = 0; for (ll i = 1; i \u003C= n; i ++) { t ++; if (i == n || !(x[i].a == x[i + 1].a && x[i].b == x[i + 1].b && x[i].c == x[i + 1].c)) { nx[++ m].a = x[i].a; nx[m].b = x[i].b; nx[m].c = x[i].c; nx[m].cnt = t; t = 0; } } CDQ(1, m); for (ll i = 1; i \u003C= m; i ++) { ans[nx[i].res + nx[i].cnt - 1] += nx[i].cnt; } for (ll i = 0; i \u003C n; i ++) { cout \u003C\u003C ans[i] \u003C\u003C '\\n'; } return 0; } 复杂度分析 时间复杂度：CDQ 分治共 \\(O(\\log n)\\) 层，每层中排序和双指针扫描均为 \\(O(n \\log n)\\)（树状数组操作 \\(O(\\log k)\\)），总计 \\(O(n \\log^2 n)\\)。 空间复杂度：\\(O(n + k)\\)。 代码细节要点 去重：完全相同的 \\((a, b, c)\\) 元素必须合并，否则会漏算。去重后记录 \\(cnt\\)，最终答案为 \\(res_i + cnt_i - 1\\)。 清空树状数组：不能用 memset（会超时），只能将用过的位置逐个减回去。这是保证复杂度的关键。 排序的等价性：在合并阶段对左右两半分别按 \\(b\\) 排序，不会破坏第一维 \\(a\\) 的大小关系（因为左半的 \\(a\\) 普遍 \\(\\le\\) 右半），这是 CDQ 分治正确性的基础。 2.4 CDQ 套 CDQ：四维偏序 四维偏序：给定 \\(n\\) 个四元组 \\((a, b, c, d)\\)，统计满足四维均 \\(\\le\\) 的点对数。 处理思路与三维偏序类似，逐维降低： 维度 处理方式 第一维 \\(a\\) 排序 第二维 \\(b\\) 外层 CDQ 分治 第三维 \\(c\\) 内层 CDQ 分治（或归并排序） 第四维 \\(d\\) 树状数组 外层 CDQ 负责按 \\(b\\) 的偏序关系分治，同时给左半元素打标记 tag=1，右半元素 tag=0。合并时按 \\(b\\) 排序后，将所有元素送入内层。 内层 CDQ 对 tag 数组按 \\(c\\) 分治归并，合并时用双指针按 \\(c\\) 排序，将 tag=1 的元素加入树状数组，tag=0 的元素查询。 \u002F\u002F 外层 CDQ：处理 b 维 void cdq2(int l, int r) { if (l == r) return; int mid = (l + r) >> 1; cdq2(l, mid); cdq2(mid + 1, r); \u002F\u002F 给左半打标记 for (int i = l; i \u003C= mid; i++) tmp[i].tag = 1; for (int i = mid + 1; i \u003C= r; i++) tmp[i].tag = 0; \u002F\u002F 按 b 归并，送入内层 CDQ 处理 c, d 两维 merge_by_b(l, mid, mid + 1, r); \u002F\u002F 归并到 tmp2[l..r] cdq_inner(l, r); \u002F\u002F 内层处理 c, d \u002F\u002F 恢复 for (int i = l; i \u003C= r; i++) \u002F* restore order *\u002F; } \u002F\u002F 内层 CDQ：处理 c 维，用树状数组处理 d 维 void cdq_inner(int l, int r) { if (l == r) return; int mid = (l + r) >> 1; cdq_inner(l, mid); cdq_inner(mid + 1, r); sort(tmp2 + l, tmp2 + mid + 1, cmpC); \u002F\u002F 按 c 排序 sort(tmp2 + mid + 1, tmp2 + r + 1, cmpC); int i = l, j = mid + 1; while (j \u003C= r) { while (i \u003C= mid && tmp2[i].c \u003C= tmp2[j].c) { if (tmp2[i].tag) bit.add(tmp2[i].d, 1); i++; } if (!tmp2[j].tag) tmp2[j].ans += bit.query(tmp2[j].d); j++; } for (int p = l; p \u003C i; p++) if (tmp2[p].tag) bit.add(tmp2[p].d, -1); } 复杂度：\\(O(n \\log^3 n)\\)。实际上常数较大，\\(n\\) 不太大时可用。 规律总结：\\(k\\) 维偏序可以用 \\(k-2\\) 层 CDQ 套 CDQ + 1 次排序 + 1 个树状数组解决，复杂度 \\(O(n \\log^k n)\\)。 2.5 动态问题转静态 CDQ 分治还有一个强大应用：将带修改的动态问题转化为离线静态问题。 典型例题：CQOI2011 动态逆序对 给定 \\(1 \\sim n\\) 的排列，按给定顺序依次删除 \\(m\\) 个元素，每次删除前求当前逆序对数。 分析： 每个元素有两个属性：值 \\(val\\) 和删除时间 \\(del\\)（未删除的设为 \\(m+1\\)）。 对于元素 \\(i\\)，删除它时损失的逆序对数 = 在它之前删除且值比它大的元素数 + 在它之后删除且值比它小的元素数。 即统计满足 \\(del_j \u003C del_i\\) 且 \\(val_j > val_i\\) 的点对——这又是一个三维偏序问题！ 三维为：时间维（删除顺序 \\(del\\)）、值维 \\(val\\)、贡献方向。 用 CDQ 分治处理时间维，归并处理 \\(val\\) 维，树状数组统计贡献。CDQ 分治的\"动态转静态\"能力，使得许多在线问题可以离线高效解决。 三、点分治 3.1 树的重心 点分治的核心在于选择一个合适的\"分治中心\"——树的重心。 定义：对于树中的某个节点 \\(u\\)，删除 \\(u\\) 后树分裂为若干子树。记其中最大子树的大小为 \\(max\\_subtree(u)\\)。使 \\(max\\_subtree(u)\\) 最小的节点 \\(u\\) 称为树的重心（centroid）。 重要性质 子树大小不超过一半：以重心为根时，所有子树的大小不超过 \\(n\u002F2\\)。这是保证分治深度为 \\(O(\\log n)\\) 的关键。 重心的唯一性：一棵树至少有 1 个重心，至多有 2 个重心。若有两个重心，则它们相邻，且树有偶数个节点。 距离和最小：树中所有点到某个点的距离和，以重心处最小。 求重心的方法 两次 DFS ： int siz[N], maxS[N], root, maxPart; \u002F\u002F 第一次 DFS：计算子树大小 void dfs1(int u, int f) { siz[u] = 1; for (int i = head[u]; i; i = nxt[i]) { int v = to[i]; if (v == f || vis[v]) continue; dfs1(v, u); siz[u] += siz[v]; } } \u002F\u002F 第二次 DFS：计算最大子树大小并找重心 void dfs2(int u, int f, int total) { maxS[u] = total - siz[u]; \u002F\u002F 向上的子树 for (int i = head[u]; i; i = nxt[i]) { int v = to[i]; if (v == f || vis[v]) continue; maxS[u] = max(maxS[u], siz[v]); dfs2(v, u, total); } if (maxS[u] \u003C maxPart) { maxPart = maxS[u]; root = u; } } \u002F\u002F 使用示例（以 1 为根，整棵树大小 n） maxPart = INF; dfs1(1, 0); dfs2(1, 0, n); \u002F\u002F root 即为重心 3.2 基本思想与算法流程 点分治适合处理大规模的树上路径信息问题。 核心思想 树上任意一条路径，必然经过某个\"中心节点\"。我们将所有路径分为两类： 经过当前根节点的路径。 不经过当前根节点的路径（完全在某个子树内部）。 对于第一类路径，可以在当前层直接统计；对于第二类路径，递归到子树中处理。 算法流程 divide(root): 1. 找到当前连通块的重心 root 2. 统计所有经过 root 的路径的贡献 3. 标记 root 为已处理（vis[root] = true） 4. 对 root 的每个子节点所在连通块，递归执行 divide 路径分类细化 对于经过根节点 \\(rt\\) 的路径，进一步细分： 以 \\(rt\\) 为一个端点的路径：只需统计 \\(rt\\) 到子树中每个节点的距离信息。 两个端点都不为 \\(rt\\) 的路径：可以由两条\"以 \\(rt\\) 为端点\"的路径合并得到（路径 \\(u \\to v\\) = 路径 \\(u \\to rt\\) + 路径 \\(rt \\to v\\)）。 因此处理过程为： 遍历 \\(rt\\) 的每个子节点 \\(ch\\)，计算 \\(ch\\) 子树中所有节点到 \\(rt\\) 的距离。 对于每条新计算的路径，检查能否与已处理的子树中路径拼接成答案。 将新路径信息加入总信息中。 注意：清空信息时不能直接 memset，应记录用过的位置然后逐个清空，保证复杂度正确。 3.3 经典应用：树上距离为 k 的路径 题目：洛谷 P3806【模板】点分治 给定一棵 \\(n\\) 个节点的带边权树，\\(m\\) 次询问，每次给出 \\(k\\)，问树上是否存在距离为 \\(k\\) 的点对。 分析 对于每个询问 \\(k\\)，检查是否存在两点距离恰为 \\(k\\)。 使用点分治：对每个分治层，统计经过当前重心的路径。 用桶 tf[d] 记录之前处理过的子树中是否存在距离重心为 \\(d\\) 的节点。 对当前子树中的每个节点 \\(i\\)（到重心距离为 \\(dist_i\\)），检查 \\(tf[k - dist_i]\\) 是否为 true。 处理顺序（关键！） 对于重心的每个子树 ch: 1. 先计算 ch 子树中所有节点到重心的距离 2. 检查这些路径能否与\"之前处理过的子树\"中的路径组合成答案 3. 将这些路径信息加入桶中 为什么要\"先查询再加入\"？因为这样可以避免同一子树内的两个节点被错误地配对（它们的路径不经过重心，而是经过子树内的某个节点，会在递归处理该子树时被统计到）。 3.4 AC code #include \u003Cbits\u002Fstdc++.h> using namespace std; const int N = 1e4 + 5; const int M = 105; const int Q = 1e7 + 7; int n, m; int head[N], ver[N * 2], nxt[N * 2], edg[N * 2], tot; void add(int u, int v, int w) { ver[++tot] = v; nxt[tot] = head[u]; head[u] = tot; edg[tot] = w; } int siz[N], maxS[N], root, maxp; bool vis[N]; void dfs1(int u, int fa) { siz[u] = 1; for (int i = head[u]; i; i = nxt[i]) { int v = ver[i]; if (v == fa || vis[v]) continue; dfs1(v, u); siz[u] += siz[v]; } } void dfs2(int u, int fa, int total) { maxS[u] = total - siz[u]; for (int i = head[u]; i; i = nxt[i]) { int v = ver[i]; if (v == fa || vis[v]) continue; maxS[u] = max(maxS[u], siz[v]); dfs2(v, u, total); } if (maxS[u] \u003C maxp) { maxp = maxS[u]; root = u; } } int dist[N], cnt; int ask[M]; bool ans[M]; bool bucket[Q]; vector\u003Cint> used; void dis(int u, int fa, int d) { dist[++ cnt] = d; for (int i = head[u]; i; i = nxt[i]) { int v = ver[i]; if (v == fa || vis[v]) continue; dis(v, u, d + edg[i]); } } void divide(int u) { dfs1(u, 0); int total = siz[u]; maxp = INT_MAX; dfs2(u, 0, total); u = root; vis[u] = true; bucket[0] = true; used.push_back(0); for (int i = head[u]; i; i = nxt[i]) { int v = ver[i]; if (vis[v]) continue; cnt = 0; dis(v, u, edg[i]); for (int j = 1; j \u003C= cnt; j ++) { for (int q = 0; q \u003C m; q ++) { if (ans[q]) continue; if (ask[q] >= dist[j] && bucket[ask[q] - dist[j]]) { ans[q] = true; } } } for (int j = 1; j \u003C= cnt; j ++) { if (dist[j] \u003C Q) { bucket[dist[j]] = true; used.push_back(dist[j]); } } } for (int d : used) { if (d \u003C Q) bucket[d] = false; } used.clear(); for (int i = head[u]; i; i = nxt[i]) { int v = ver[i]; if (vis[v]) continue; divide(v); } } int main() { ios::sync_with_stdio(false); cin.tie(0); cin >> n >> m; for (int i = 1; i \u003C n; i ++) { int u, v, w; cin >> u >> v >> w; add(u, v, w); add(v, u, w); } for (int i = 0; i \u003C m; i ++) cin >> ask[i]; divide(1); for (int i = 0; i \u003C m; i ++) { if (ans[i]) cout \u003C\u003C \"AYE\\n\"; else cout \u003C\u003C \"NAY\\n\"; } return 0; } 复杂度分析 找重心：每层 \\(O(n)\\)（DFS 遍历整个连通块）。 统计路径：每层 \\(O(n)\\)（遍历所有节点计算距离 + 查询）。 递归深度：由于每次选重心，子树大小不超过 \\(n\u002F2\\)，递归深度为 \\(O(\\log n)\\)。 总复杂度：\\(O(n \\log n)\\)（对于本题路径存在性查询），或 \\(O(n \\log^2 n)\\)（若需要排序或使用数据结构）。 关键点：选择重心保证了递归深度为 \\(O(\\log n)\\)，这是点分治效率的保障。如果随意选根（如总是选 1 号节点），在最坏情况（链状树）下递归深度退化为 \\(O(n)\\)，总复杂度退化为 \\(O(n^2)\\)。 代码细节要点 两次 DFS 找重心：第一次计算 total（当前连通块大小），第二次根据 total 找重心。注意每次递归到子树时，必须重新计算子树大小作为新的 total。 清空桶的方式：不能使用 memset（会导致 \\(O(\\text{桶大小})\\) 的清空开销）。正确做法是记录所有修改过的桶位置，然后逐个清零。 查询先于加入：处理每个子树时，先查询该子树路径能否与之前子树的路径组合成答案，再将该子树路径加入桶。这保证了统计的路径一定经过重心。 vis 数组：标记已处理的重心节点，确保递归时不会回到上层。 3.5 扩展：点分治统计距离不超过 k 的点对数 题目：洛谷 P4178 Tree 给定带权树，求距离 \\(\\le k\\) 的点对数量。 这与上面的\"存在性查询\"不同，需要计数。处理方式： 对每个子树，收集所有节点到重心的距离。 对距离数组排序，用双指针统计满足 \\(dist_i + dist_j \\le k\\) 的对数。 减去同一子树内的重复计数（容斥）。 int countPairs(int dist[], int top, int k) { sort(dist + 1, dist + top + 1); int cnt = 0, r = top; for (int l = 1; l \u003C r; ) { if (dist[l] + dist[r] \u003C= k) { cnt += r - l; l++; } else { r--; } } return cnt; } 复杂度：排序 \\(O(n \\log n)\\)，每层 \\(O(n \\log n)\\)，总计 \\(O(n \\log^2 n)\\)。 四、对比与总结 CDQ 分治 vs 点分治 对比项 CDQ 分治（序列分治） 点分治（树分治） 适用场景 序列上的偏序问题、DP 优化、动态转静态 树上路径统计 分治对象 序列的下标区间 \\([l, r]\\) 树的连通块（以重心为根） 消除的维度 时间维（下标顺序 \\(i \u003C j\\)） 空间维（路径必经过重心） 分治中心 区间中点 \\(mid\\) 树的重心 保证深度的机制 区间自然二分 重心保证子树 \\(\\le n\u002F2\\) 合并方式 排序 + 双指针 + 数据结构 桶 \u002F 排序 + 双指针 + 数据结构 典型复杂度 \\(O(n \\log^2 n)\\) \\(O(n \\log n)\\) 或 \\(O(n \\log^2 n)\\) 共同核心思想 两者的本质都是通过分治的结构天然消除一个维度： CDQ 分治：分治中点 \\(mid\\) 保证了左半下标 \\(\u003C\\) 右半下标，天然满足 \\(i \u003C j\\) 的偏序。 点分治：选择重心作为根，所有经过重心的路径可以拆分为两条子路径，天然保证路径经过当前层处理的节点。 分治三步法总结 无论是 CDQ 分治还是点分治，都遵循分治的三步范式： 分：将问题划分为独立子问题（CDQ 按区间中点分；点分治按重心分子树）。 治：递归处理子问题（处理左\u002F右区间；处理各子树）。 合：合并子问题解（处理跨区间点对；处理经过重心的路径）。 学习建议 先掌握基础：归并排序、树状数组、树的 DFS 是前置知识，务必扎实。 理解\"消除维度\"：这是 CDQ 分治和点分治最核心的直觉——分治结构本身就是一个维度被处理掉了。 注意清空方式：无论是 CDQ 的树状数组还是点分治的桶，都不能用 memset 暴力清空，要记录修改过的位置逐个还原。 注意处理顺序：CDQ 中\"先递归再合并\"，点分治中\"先查询再加入桶\"，顺序的正确性是算法正确的基础。 多写模板题：三维偏序（P3810）和点分治模板（P3806）是入门必刷题，务必独立 AC。",9506,{"id":6,"kind":7,"title":11,"summary":13,"image":14,"href":15,"meta":36,"badge":10,"author":12,"stats":-1,"accent":37,"coverRatio":38,"tags":39},"2026 · 综合技术","#2563eb","16 \u002F 10",[18],{"targetType":8,"targetId":9,"likedByMe":41,"likeCount":42,"commentCount":42,"contentLikeCount":42,"contentCommentCount":42,"sourceLikeCount":42,"sourceCommentCount":42},false,0,[44,51,58,65,72,78,85,92],{"id":45,"kind":7,"title":46,"summary":47,"image":48,"href":49,"meta":36,"badge":10,"author":12,"stats":-1,"accent":37,"coverRatio":38,"tags":50},"NEWS_ARTICLE:838","AI 学习笔记：LLM 的微调实验","title: LLM 的微调实验 author: 凌杰 date: 2026-08-21 tags: LoRA, LLaMA-Factory, Qwen categories: 人工智能 [!NOTE] 笔记说明 这篇笔记对应的是《[[关于 AI 的学习路线图]]》一文中所规划的第三个学习阶段。其中","https:\u002F\u002Fimg2024.cnblogs.com\u002Fblog\u002F691082\u002F202609\u002F691082-20260902122904258-1285666483.png","\u002Fnews\u002F838",[18],{"id":52,"kind":7,"title":53,"summary":54,"image":55,"href":56,"meta":36,"badge":10,"author":12,"stats":-1,"accent":37,"coverRatio":38,"tags":57},"NEWS_ARTICLE:843","介绍一下常用的Token鉴权方案","本文梳理 Session‑Cookie、JWT、OAuth2.0、SSO 主流 Token 鉴权方案，对比各方案适用场景。重点讲解生产级 JWT 双 Token 架构，给出 RS256 非对称加密、Redis 黑名单、网关统一鉴权等 Java 实战代码。剖析 JWT 注销、并发刷新、令牌泄露等落地痛","https:\u002F\u002Fimg2024.cnblogs.com\u002Fblog\u002F739056\u002F202609\u002F739056-20260902114155245-1924311389.png","\u002Fnews\u002F843",[18],{"id":59,"kind":7,"title":60,"summary":61,"image":62,"href":63,"meta":36,"badge":10,"author":12,"stats":-1,"accent":37,"coverRatio":38,"tags":64},"NEWS_ARTICLE:863","弱模型不能裸奔：Agent Harness 凭什么真实有效","Harness 不是给弱模型贴的创可贴。它是把工程纪律——验证、门禁、不变量、路由——变成架构里一等公民的方式。模型每半年换一代，今天省钱的 Flash 明天可能就过时了，但那套'默认怀疑、机器校验、按决策密度调度'的流程会留下来，并且越跑越值钱。\n弱模型不能裸奔。给它穿上 harness，便宜才真","https:\u002F\u002Fimg2024.cnblogs.com\u002Fblog\u002F510\u002F202608\u002F510-20260830181633785-1171146572.jpg","\u002Fnews\u002F863",[18],{"id":66,"kind":7,"title":67,"summary":68,"image":69,"href":70,"meta":36,"badge":10,"author":12,"stats":-1,"accent":37,"coverRatio":38,"tags":71},"NEWS_ARTICLE:865","焕新鸿蒙应用权限管理方案，应用授权体验再升级","作为用户或应用开发者，或许经历过类似的体验场景：使用应用的过程中，触发应用某些功能会需要访问你的位置、麦克风、相机等常用权限，若为了保护隐私拒绝授权后，想要使用功能时，再次打开却找不到设置入口；或开启流程繁琐，需要经过频繁跳转和设置。这一问题不仅影响用户体验，还可能造成应用功能不可用、用户流失，也制","https:\u002F\u002Fimg2024.cnblogs.com\u002Fblog\u002F2396482\u002F202609\u002F2396482-20260901170248450-744043562.png","\u002Fnews\u002F865",[18],{"id":73,"kind":7,"title":74,"summary":75,"image":14,"href":76,"meta":36,"badge":10,"author":12,"stats":-1,"accent":37,"coverRatio":38,"tags":77},"NEWS_ARTICLE:875","别急着翻译 SKILL.md：我做了一个专门拆解 Skill 设计的工具","别急着翻译 SKILL.md：我做了一个专门拆解 Skill 设计的工具 第一次打开一份复杂的 SKILL.md，很多人的反应都是从头往下读。 每句话似乎都认识，连在一起却不一定明白：为什么这里用了 MUST？为什么执行前要读取这些文件？状态由谁维护？AI 做到什么程度才算完成？如果两条指令发生冲突","\u002Fnews\u002F875",[18],{"id":79,"kind":7,"title":80,"summary":81,"image":82,"href":83,"meta":36,"badge":10,"author":12,"stats":-1,"accent":37,"coverRatio":38,"tags":84},"NEWS_ARTICLE:879","高并发下的抢红包设计：微信红包背后的算法与温情","本文拆解微信拼手气红包高并发实现，详解核心**二倍均值算法**，解决红包分配公平性问题。架构上依靠 Redis+Lua 脚本实现原子抢红包，规避超发与重复抢夺；结合 MQ 异步落库、热点 key 拆分、多层限流、定时对账兜底，支撑百万 QPS。给出 Java、Lua 核心源码，梳理超发、缓存一致性、","https:\u002F\u002Fimg2024.cnblogs.com\u002Fblog\u002F739056\u002F202609\u002F739056-20260901095345589-1940958188.png","\u002Fnews\u002F879",[18],{"id":86,"kind":7,"title":87,"summary":88,"image":89,"href":90,"meta":36,"badge":10,"author":12,"stats":-1,"accent":37,"coverRatio":38,"tags":91},"NEWS_ARTICLE:896","如何设计对外API接口","详解生产级对外 API 完整设计方案，围绕易用、安全、健壮黄金三角，覆盖 RESTful 规范、统一响应、签名验签防重放、AOP 注解实现分布式幂等、Redis 令牌桶限流、版本兼容、监控文档。附带可直接复用 Java 核心代码，梳理面试高频技术难点，搭配模拟面试场景，帮你避开线上坑点，快速掌握开放","https:\u002F\u002Fimg2024.cnblogs.com\u002Fblog\u002F739056\u002F202608\u002F739056-20260831134132446-55355388.png","\u002Fnews\u002F896",[18],{"id":93,"kind":7,"title":94,"summary":95,"image":14,"href":96,"meta":36,"badge":10,"author":12,"stats":-1,"accent":37,"coverRatio":38,"tags":97},"NEWS_ARTICLE:906","上周热点回顾（8.24-8.30）","热点随笔： &#183; 从 PostgreSQL 到 Kubernetes：开源的护城河，从来不写在代码里 (张善友) &#183; 代码都能让AI写了，我还学个屁？ (佛祖让我来巡山) &#183; Vibe Coding 月提交量 29 亿次之后：GitHub 的危机、Azure 迁移，以及","\u002Fnews\u002F906",[18]]