2026.9

QOJ 8049 Equal Sums

简要题意

Little Cyan Fish 有 \(n+m\) 个整数,分别记为 \(x_1, x_2, \dots, x_n\)\(y_1, y_2, \dots, y_m\)。Little Cyan Fish 已知:

对于所有 \(1 \le i \le n\),满足 \(l_i^{(x)} \le x_i \le r_i^{(x)}\)

对于所有 \(1 \le j \le m\),满足 \(l_j^{(y)} \le y_j \le r_j^{(y)}\)

Little Cyan Fish 尚未确定 \(x_i\)\(y_j\) 的具体取值。他想知道,对于任意一对整数 \((a, b)\) (\(1 \le a \le n, 1 \le b \le m\)),有多少种方式可以设定 \(x_i\) (\(1 \le i \le a\)) 和 \(y_j\) (\(1 \le j \le b\)) 的值,使得 \(\sum_{i=1}^a x_i = \sum_{j=1}^b y_j\)。由于结果可能非常大,你只需要输出其对 \(998\,244\,353\) 取模后的结果。

\(n,V \le 500\)

解题报告

神了。

第一眼看到这个题会有一百万种转换思路。到底是怎么想到的?

考虑如何刻画一个 \(\sum_{i=1}^a x_i = \sum_{j=1}^b y_j\)。我们将其看作一种三维空间里 \((0,0,0)\)\((a,b,0)\) 的一条路径:

假设目前为 \((i,j,S)\)

  • \(S\ge 0\),变为 \((i,j+1,S-y_{j+1})\)
  • \(S < 0\),变为 \((i+1,j,S+x_{i+1})\)

容易发现这样总共只有 \(O(n^2V)\) 个不同的点。起点是相同的,直接 dp 可以简单优化到 \(O(n^2V)\)


QOJ 8050 Random Permutation

简要题意

给定一个长度为 \(n\) 的排列 \(a\),求所有 \(1\le l \le r \le n\), \([a_{l},a_{l+1},\cdots,a_{r}]\) 的中位数之和。保证数据随机。

\(n \le 100000\)

解题报告

难点在于猜到复杂度是对的。

考虑枚举中位数的值 \(x\)。将 \(\ge x\) 的值记为 1,将 \(<x\) 的值记为 -1,则一个区间的中位数为 \(x\) 当且仅当和为 1 或 2。记所有中位数为 \(x\) 区间的并为 \([l_x,r_x]\),则容易做到 \(O(\sum_x {r_x-l_x})\)。由于随机数据,我们猜测 \(\sum_x {r_x-l_x}\)\(O(n^{1.5})\) 的,实际也确实如此。找出 \(l_x,r_x\) 可以简单线段树二分求出。


QOJ 8179 2D Parentheses

简要题意

在二维平面上,有 \(N\) 个左括号和 \(N\) 个右括号。第 \(i\) 个左括号的坐标为 \((x_{1,i}, y_{1,i})\),第 \(i\) 个右括号的坐标为 \((x_{2,i}, y_{2,i})\)

你需要执行 \(N\) 次以下操作来在平面上排列矩形:

在平面上选择一个左括号和一个右括号。如果所选左括号的坐标为 \((x_1, y_1)\),所选右括号的坐标为 \((x_2, y_2)\),则必须满足 \(x_1 < x_2\)\(y_1 < y_2\)

从平面上移除所选的左括号和右括号,并在平面上放置一个顶点位于 \((x_1, y_1), (x_1, y_2), (x_2, y_2), (x_2, y_1)\) 的矩形。
确定是否可以以满足以下条件的方式排列 \(N\) 个矩形。如果可以,请提供一种这样的排列方式。

对于任意两个不同的矩形,它们的公共面积要么为 0,要么其中一个完全包含在另一个内部。

\(n \le 3\times 10^5\)

解题报告

实际上好像并不难?该学习贪心了。

考虑对所有点按先按 \(y\) 从大排序,相同再按 \(x\) 从大到小排序,然后每个左括号去匹配 \(x\) 坐标最小且 \(y\) 坐标最小的未匹配的右括号。由于选其他都会导致矩形相交,只需要判断这个方案的合法性就行了。

对于判合法性,考虑扫描线,只用维护所有与 \(y\) 轴平行直线的 \(x\) 坐标就行了。

时间复杂度 \(O(n \log n)\)


QOJ 8180 Bridge Elimination

简要题意

给定 \(n\) 个点,每个点有权值 \(a_i\)。对于所有 \(2^{\frac {n(n-1)}2}\) 中加边的方案,定义这个方案的权值为每个边双的点权和的积。求所有加边方案的权值的和。

\(n \le 400\)

解题报告

套路题。

首先把这个权值拆掉。我们要求出两个数组 \(p_i,q_i\)\(p_i\) 表示从 \(n\) 个点中选出恰好 \(i\) 个点的点权积的和,\(q_i\) 表示将有多少种选边方案,使所有点恰好被分为 \(i\) 个边双,\(1,2,\cdots,i\) 分别恰好属于其中一个边双。则答案显然为 \(\sum_{i=1}^n p_iq_i\)

显然有 \(p_i=[x^i]\prod_{i=1}^n (1+a_ix)\),可以简单 \(O(n\log^2n)\)\(O(n^2)\) 求出。

\(q\) 会稍微复杂一点。首先你需要求出 \(g_i\) 表示 \(i\) 个点的点双个数,而求 \(g\) 又需要求 \(h_i\),表示 \(i\) 个点的连通图个数。

对于求 \(h\) ,对包含 1 的连通块大小容斥,有 \(h_i=2^{\frac {i(i-1)}2}-\sum_{j=1}^{i-1}\binom{i-1}{j-1}h_j2^{\frac {(i-j)(i-j-1)}2}\)。这部分是 \(O(n^2)\) 的。

对于求 \(g\),首先,我们有关于 prufer 序列的经典结论:用 \(m-1\) 条边将 \(m\) 个连通块连成一个大连通块,假设第 \(i\) 个连通块有 \(c_i\) 个点,共 \(n\) 个点,方案数为 \(n^{m-2}\prod_{i=1}^m c_i\)。考虑记录辅助数组 \(dp_{i,j}\) 容斥,则有:

\[dp_{i,j}=\sum_{k=1}^i \binom{i-1}{k-1}dp_{i-k,j-1}kg_k \]

\[g_i=h_i-\sum_{j=2}^i dp_{i,j}i^{j-2} \]

这部分是 \(O(n^3)\) 的。

最后到求 \(q\),记辅助数组 \(dp2_{i,j}\),有

\[dp2_{i,j}=\sum_{k=1}^{i-j+1} \binom{i-j}{k-1} dp2_{i-j,k-1}kg_k \]

\[q_i=dp2_{n,i} n^{i-2} \]

注意特判 \(q_1\)。这部分也是 \(O(n^3)\) 的。

因此总共就是 \(O(n^3)\)


QOJ 8175 Dense Planting

简要题意

给定正整数 \(K\),输出一个点数 \(\le 100\),边数 \(\le 1000\) 的有向图,允许重边,使这个图的生成树数量为 \(K\)

\(1\le K \le 10^9\)

解题报告

纯粹的乱搞题。以下提供两种参考的乱搞。

方法一:

考虑将图设定为以下形态:

其中边权代表有多少条重复的边。

由矩阵树定理容易得出生成树个数为 \((ab-e^2)(cd-f^2)-ad\)。合理枚举各个参数即可。

方法二:

定义 \(solve(A,B,x,y)\),这个函数将返回一个图,包含 \(A\) 棵生成树,\(B\) 个 两个特殊端点 \((x,y)\) 被分到不同连通块的生成二森林。

构造方法为:

\(A>B\) ,先调用 \(solve(A-B,B,x,y)\) ,再将 \(x\)\(y\) 连一条边。

否则,先调用 \(solve(A,B-A,x,y)\),新建一个点 \(t\),令 \(y\)\(t\) 连一条边,再令 \(y \leftarrow t\)

在第一种情况中,显然生成二森林个数不变,包含新边的生成树有 \(B\) 棵,不包含的有 \(A-B\) 棵。

第二种情况是类似的,分讨 \(x\to y\)\(y\to t\) 哪条边被生成二森林包含即可。

边界条件是 \(solve(1,1,x,y)\),此时新建两个点相连即可。不妨假设 \(solve(0,k,x,y)\)\(solve(k,0,x,y)\) 无解。

我们需要求的是 \(solve(k,p,x,y)\),其中 p 为任意正整数。随机 \(p\) 直到得出合法解即可。

两个做法感觉都不是人类能想出来的。


QOJ 11543 Path Partition

简要题意

提交答案题。

给定 \(n\) 个点,\(m\) 条边的图,保证 \(m\) 是 3 的倍数,给出一种将边划分为若干边数为三的路径的方案。

注意这是 NP-complete 的。数据保证随机,\(n \le 90000,m \le 3\times 10^5\)。下发数据,共 10 组,数据有梯度。

解题报告

乱搞题滚出 OI。

首先,对于 sub 1 (\(n=771\) 的完全图),做法是跑一个欧拉回路,三个三个划分为段。这启发我们关注点的度数。

于是现在变成,我们要删掉若干条路径,使所有点度数变为偶数,且每个连通块有 3 的倍数条边。

后面做不了了,考虑一个乱搞。

每次暴力找一个长度为 3,两边为奇点的路径删掉大概有 40-50 分。



考虑一个乱搞:考虑每次找出度数最小的奇点,随便找一条这个点开始的路径删掉,直到没有其他奇点为止。

对于这个做法,选取合适的随机种子即可通过。

具体的是,度数最小的时候随机找一个点,然后路径的找法是开局按边数从大到小排序,每次找最前面的未被删除边。

这样本地随 4-5 次就能找出一个合法的随机种子。当然你也可以把找种子的过程也也写到程序里。

这里提供一份 checker:

checker.cpp
#include <bits/stdc++.h>
#include "testlib.h"
using namespace std;

int main(int argc, char* argv[]) {
    registerTestlibCmd(argc, argv);

    int n = inf.readInt();
    int m = inf.readInt();

    // edge id: normalized (min(u,v), max(u,v))
    map<pair<int, int>, int> edge_id;

    for (int i = 0; i < m; ++i) {
        int u = inf.readInt();
        int v = inf.readInt();
        if (u > v) swap(u, v);

        auto p = make_pair(u, v);

        // 题目保证无重边,这里顺手检查一下
        if (edge_id.count(p)) {
            quitf(_fail, "Input contains multiple edges.");
        }

        edge_id[p] = i;
    }

    vector<bool> used(m, false);

    auto get_edge_id = [&](int u, int v) -> int {
        if (u > v) swap(u, v);
        auto it = edge_id.find({u, v});
        if (it == edge_id.end()) return -1;
        return it->second;
    };

    for (int i = 0; i < m / 3; ++i) {
        int a = ouf.readInt();
        int b = ouf.readInt();
        int c = ouf.readInt();
        int d = ouf.readInt();

        // 点编号检查
        if (a < 1 || a > n ||
            b < 1 || b > n ||
            c < 1 || c > n ||
            d < 1 || d > n) {
            quitf(_wa,
                  "Invalid vertex in group %d: %d %d %d %d",
                  i + 1, a, b, c, d);
        }

        int e1 = get_edge_id(a, b);
        int e2 = get_edge_id(b, c);
        int e3 = get_edge_id(c, d);

        // 三条边必须都存在
        if (e1 == -1) {
            quitf(_wa,
                  "Group %d uses nonexistent edge {%d,%d}.",
                  i + 1, a, b);
        }

        if (e2 == -1) {
            quitf(_wa,
                  "Group %d uses nonexistent edge {%d,%d}.",
                  i + 1, b, c);
        }

        if (e3 == -1) {
            quitf(_wa,
                  "Group %d uses nonexistent edge {%d,%d}.",
                  i + 1, c, d);
        }

        // 同一个颜色内不能重复使用同一条边
        if (e1 == e2 || e1 == e3 || e2 == e3) {
            quitf(_wa,
                  "Group %d contains the same edge multiple times: "
                  "%d %d %d %d",
                  i + 1, a, b, c, d);
        }

        // 每条输入边最多使用一次
        if (used[e1]) {
            quitf(_wa,
                  "Edge {%d,%d} is used more than once.",
                  a, b);
        }

        if (used[e2]) {
            quitf(_wa,
                  "Edge {%d,%d} is used more than once.",
                  b, c);
        }

        if (used[e3]) {
            quitf(_wa,
                  "Edge {%d,%d} is used more than once.",
                  c, d);
        }

        used[e1] = true;
        used[e2] = true;
        used[e3] = true;
    }

    // 每条输入边都必须恰好使用一次
    for (int i = 0; i < m; ++i) {
        if (!used[i]) {
            quitf(_wa, "Edge %d is not used.", i + 1);
        }
    }

    quitf(_ok, "Valid decomposition.");
}
AI 总结

思维断点

**** 之前最后得到的结论是:可以把问题转化成不断删掉若干条 3 边路径,让剩余图所有点度数为偶、且每个连通块边数为 3 的倍数;顺着这个思路能写出的自然实现是“每次暴力找一条两端点当前都是奇点的 3 边路径删掉”,实测约 40–50 分,随后就推进不下去。

**** 之后标准做法的第一个关键变化,并不是发明了新模型,而是把操作放宽成“每次从当前度数最小的奇点出发删掉一条 3 边路径”,并且让路径的选择服从固定优先级(而不是每步完全随机)。这个放宽背后隐含的重新表述是:删路径不一定是在“当场消灭一对奇点”,而是在把奇偶性沿路径搬运。

为什么会想到不下去

  1. “两端都是奇点”是过度约束。 删一条 u–v–x–y 路径只会让两个端点各少一条边、中间点各少两条边,所以端点奇偶性翻转、中间点奇偶性不变。若只允许“两端都是奇点”,就要求当前图里恰好已经存在一对相距 3 条边的奇点;找不到这种现成配对时算法立刻卡死。实际上完全可以让起点是奇点、终点暂时是偶点:这样奇点数不减少,但奇点“移动”到了终点,为以后与另一个奇点相撞抵消创造了条件。把奇点看成可以搬运、碰撞的记号,而不是每一步都必须消灭的对象,才能理解这个乱搞为什么有继续走下去的能力。

  2. 把中间点当成免费的中转站。 一次删除里真正消耗最多的是中间两个点(各少 2 条边)。一个低度奇点如果被当作中间点穿过,剩余度数会迅速变小,之后它的“出路”几乎被堵死。标准做法“先找度数最小的奇点、沿按度数排序后最靠前的未删邻居走”,本质是“最受约束者优先 + 尽量不消耗稀缺资源”;这只有在意识到失败形态是“某些低度奇点被饿死”之后才会自然浮现。若把失败一律归因于运气,就不会想到去设计这种优先级。

  3. 随机化用错了层级。 “每次随便找一条”把随机性放在单步决策上,步数一多失败模式无法控制,看起来只能靠碰运气。正确做法是让单步选择完全确定(固定排序、固定取边规则),只把随机性保留在全局的初始打乱/种子这一层,于是一次运行的成功与否只由种子决定,可以本地换 4–5 个种子重跑。对“数据保证随机 + 提交答案题 + 可以离线反复尝试”这些条件,这是一个关键但容易被忽略的视角。

真正缺少的那一步

要把“找一条两端都是奇点的 3 边路径并删除”重新理解为“从一个奇点出发删一条 3 边路径,奇偶性搬到另一端(若另一端也是奇点则两者同时消失)”,即允许奇点在图中移动、再找机会抵消;随后用“度数最小的奇点先走、尽量沿度数大的邻居走”来保证低度奇点不被饿死,并把随机性收敛成 4~5 次种子重试。

如何从原思路逼近这个观察

  • 当“两端都是奇点”的路径不存在而图上仍有奇点时,问自己:一个奇点能不能通过删路径先“换个位置”?删掉一条以它为端点的 3 边路径后,除了它以外还有哪些点的度数或奇偶性发生了变化?
  • 一次失败后不要只记“失败了”,去观察失败时剩下的奇点长什么样:是不是大多度数极小、或者曾经被别的路径穿过?这能直接指向“中间点一次消耗 2 条边、低度点不能当中间点”。
  • 如果“每步随机”成功率低,问自己:把随机性从每一步移到“只随机初始顺序”后,失败是否变得完全可复现?可复现就意味着可以枚举几个种子,而不是靠单次运行的人品。
  • 提交答案题里,正确性不需要“单次运行保证”,需要的是“能离线重试直到成功”;复杂度卡住的地方可以靠这个前提绕过去。

QOJ 8131 Filesystem

简要题意

有一个大小为 \(m\) 的集合,元素均为 \([1,n]\) 内的整数,同时给定排列 \(a\)。求出最少将集合划分为多少个子集,每个子集是一个连续的正整数段或 \(a\) 的一个子段。

\(n \le 100000\)

解题报告

点减边容斥一下,变成最小化 \(\sum{p_i}+\sum{q_i}\),其中 \(p_i\) 表示 \(i\)\(i-1\) 是否被选进同一个正整数段中,\(q_i\) 表示 \(a_i\)\(a_{i-1}\) 是否被选进同一个 \(a\) 的子段中,答案即为 \(m-\sum{p_i}+\sum{q_i}\)

然后观察一下,所有对 \(p\)\(q\) 的限制都形如 \(p_x\)\(q_y\) 不能同时为 1。因此这是二分图匹配,上网络流即可。限制总数是 \(O(m)\) 的,复杂度 \(O(m\sqrt{n})\)

QOJ 8130 Yet Another Balanced Coloring Problem

简要题意

给定两棵分别有 \(n\) 个和 \(m\) 个顶点的有根树。顶点的编号分别为 \(1 \dots n\)(对应第一棵树)和 \(1 \dots m\)(对应第二棵树),且根节点分别为 \(n\)\(m\)。两棵树都有 \(k\) 个叶子节点,且在这两棵树中,叶子节点恰好都是编号为 \(1 \dots k\) 的顶点。在此处,树的根节点不被视为叶子节点,即使它只有一个邻居。

对于每个 \(i \in 1 \dots k\),你需要选择红色或蓝色。然后,你需要将两棵树中编号为 \(i\) 的顶点涂上所选的颜色。

在对叶子节点进行涂色后,两棵树必须满足以下条件:对于每个顶点 \(u\),其子树中红色叶子节点的数量与蓝色叶子节点的数量之差的绝对值不超过 1。

\(n \le 100000\)

解题报告

遇到这种题可以往欧拉回路想,欧拉回路有非常多非常好的性质。

对于这题,欧拉回路最有用的性质是出度等于入度,考虑给图定向,可以用一个点与父亲的连边的方向来刻画两种颜色个数的差。将每对对应的叶子连起来,可以用这条边的方向刻画这个叶子的颜色。

由于最后的图需要存在欧拉回路,所有点度数需要为偶数。因此,考虑统计出每个点的子树内有多少个叶子,记为 \(siz_i\)。对这两棵树形成的图做如下处理:

  1. 每对对应的叶子连起来。
  2. 如果 \(siz_i\) 为偶数,则删除 \(i\)\(fa_i\) 相连的边。
  3. 如果 \(k\) 为奇数,将两棵树的根连起来。

容易发现对于最终的图,所有点的度数都是偶数。对每个连通块跑一次欧拉回路,将图按照欧拉回路定向,每个对应叶子的边的方向即为答案。

证明:这样处理后,第一棵树的一个点 \(i\) 的红色叶子节点的数量与蓝色叶子节点的数量之差相当于不算 \(i \to f_i\) 的边时出度与入度的差,第二棵树上的则是它的相反数。这显然只有 \(\{-1,0,1\}\) 三种取值。

怎么还有二分图染色做法???

posted @ 2026-09-09 10:14  KSPMAT  阅读(2)  评论(0)    收藏  举报