1 条题解
-
0
题解:「USACO 2024 Jan Platinum P3」Mooball Teams III(基于提供代码)
核心思路总览
本题是 计算几何 + 线段树组合问题,核心通过“总合法方案 - 不合法方案”的容斥思想求解。先计算所有可能被直线分隔的方案总数,再减去无法被水平/垂直直线分隔的方案,最终复杂度 (O(N \log N)),适配 (N=2 \times 10^5) 的规模。
关键背景与容斥逻辑
1. 合法方案的定义
两支非空队伍(红队、蓝队)能被一条水平或垂直的非整数坐标直线分隔。例如:
- 垂直直线 (x=k+0.5) 分隔:红队全在左侧((x \leq k)),蓝队全在右侧((x \geq k+1)),或反之。
- 水平直线 (y=k+0.5) 分隔:红队全在下方((y \leq k)),蓝队全在上方((y \geq k+1)),或反之。
2. 容斥原理
- 总合法方案:所有可能被水平或垂直直线分隔的方案数。
- 不合法方案:只能被水平或垂直直线分隔,但实际无法被任何直线分隔的方案(需减去重复计算的部分)。
- 代码通过“总可能方案 - 不合法方案”计算最终答案,避免直接枚举的高复杂度。
核心状态与数组定义
数组/变量 含义 x[i], y[i]奶牛 (i) 的坐标,其中 (x_i) 和 (y_i) 均为 (1\ldots N) 的排列。 ord[x] = y映射:(x) 坐标对应的 (y) 坐标(因 (x_i) 是排列,可直接索引)。 pw2[i]预处理 (2^i \mod \text{MOD}),用于快速计算子集数。 sum总可能方案数(初始包含所有可被水平或垂直分隔的方案)。 ans不合法方案数(需从 sum中减去)。sgt[p][k]线段树节点 (p) 的状态: k=0(全0)、1(全1)、2(0→1)、3(1→0)、4(无限制),表示区间内队伍分配的约束。typ[i]线段树叶子节点的类型(初始为0,动态更新为1或2)。 完整代码(带关键注释)
#include <bits/stdc++.h> using namespace std; int n, x[205000], y[205000], ord[205000], typ[205000]; const long long mod = 1000000007; long long pw2[205000], sum, ans; // 线段树状态:0(全0), 1(全1), 2(0→1), 3(1→0), 4(无限制) long long sgt[805000][5]; // 线段树合并:计算父节点状态 from 左右子节点 void pushup(int p) { // 状态0:全0(左0且右0,或左0右无限制,或左无限制右0) sgt[p][0] = (sgt[p*2][0] * sgt[p*2+1][0] + sgt[p*2][0] * sgt[p*2+1][4] + sgt[p*2][4] * sgt[p*2+1][0]) % mod; // 状态1:全1(类似状态0) sgt[p][1] = (sgt[p*2][1] * sgt[p*2+1][1] + sgt[p*2][1] * sgt[p*2+1][4] + sgt[p*2][4] * sgt[p*2+1][1]) % mod; // 状态2:0→1(左0→1且右1/无限制,或左0/无限制且右0→1,或左0且右1) sgt[p][2] = (sgt[p*2][2] * (sgt[p*2+1][1] + sgt[p*2+1][4]) + (sgt[p*2][0] + sgt[p*2][4]) * sgt[p*2+1][2] + sgt[p*2][0] * sgt[p*2+1][1]) % mod; // 状态3:1→0(类似状态2) sgt[p][3] = (sgt[p*2][3] * (sgt[p*2+1][0] + sgt[p*2+1][4]) + (sgt[p*2][1] + sgt[p*2][4]) * sgt[p*2+1][3] + sgt[p*2][1] * sgt[p*2+1][0]) % mod; // 状态4:无限制(左右均无限制) sgt[p][4] = sgt[p*2][4] * sgt[p*2+1][4] % mod; } // 构建线段树:初始所有叶子为状态0(未限制) void build(int p, int s, int t) { if (s == t) { sgt[p][0] = sgt[p][1] = sgt[p][2] = sgt[p][3] = 0; sgt[p][typ[s]] = 1; // typ初始为0,故sgt[p][0] = 1 sgt[p][4] = 1; // 无限制状态初始为1 return; } int mid = (s + t) / 2; build(p*2, s, mid); build(p*2+1, mid+1, t); pushup(p); } // 更新线段树:将位置x的类型改为y(0/1/2) void settyp(int p, int s, int t, int x, int y) { if (s == t) { typ[s] = y; // 重置所有状态 sgt[p][0] = sgt[p][1] = sgt[p][2] = sgt[p][3] = 0; if (typ[s] <= 1) { sgt[p][typ[s]] = 1; // 类型0或1:对应状态直接置1 sgt[p][4] = 1; // 保留无限制状态 } else { sgt[p][1] = 1; // 类型2:强制状态1 sgt[p][4] = 0; // 取消无限制 } return; } int mid = (s + t) / 2; if (x <= mid) settyp(p*2, s, mid, x, y); else settyp(p*2+1, mid+1, t, x, y); pushup(p); } int main() { // 预处理2的幂次模mod pw2[0] = 1; for (int i = 1; i <= 200050; i++) { pw2[i] = pw2[i-1] * 2 % mod; } scanf("%d", &n); for (int i = 1; i <= n; i++) { scanf("%d%d", &x[i], &y[i]); ord[x[i]] = y[i]; // x是排列,建立x→y的映射 } // 计算总可能方案sum:所有可被水平或垂直直线分隔的方案 for (int i = 1; i < n; i++) { // 垂直分隔:左侧i个,右侧n-i个,左右各选非空子集,红左蓝右或红右蓝左(×2) // 水平分隔同理,但此处先按垂直计算,后续×4(垂直2种+水平2种) sum = (sum + pw2[i-1] * (pw2[n - i] - 1)) % mod; } sum = sum * 4 % mod; // ×4:垂直2种方向 + 水平2种方向 // 初始化线段树:所有节点类型为0 build(1, 1, n); // 计算不合法方案ans:无法被垂直直线分隔的方案 for (int i = 1; i < n; i++) { // 第i步:将x≤i的点(y=ord[1..i])设为类型2(强制为1) settyp(1, 1, n, ord[i], 2); // 累加状态2(0→1)和状态3(1→0):这些是无法被垂直分隔的方案 ans = (ans + sgt[1][2] + sgt[1][3]) % mod; // 恢复为类型1(后续步骤继续累加) settyp(1, 1, n, ord[i], 1); } // 不合法方案×2(水平方向同理),从总和中减去 ans = ans * 2 % mod; ans = (sum + mod - ans) % mod; // 确保非负 printf("%lld\n", ans); return 0; }核心流程拆解
1. 预处理与总方案计算
- 幂次预处理:
pw2[i]存储 (2^i \mod \text{MOD}),用于快速计算子集数量(每个奶牛有“选/不选”两种状态)。 - 总方案
sum:计算所有可被垂直或水平直线分隔的方案:- 垂直分隔:对每个 (i)((1 \leq i < N)),左侧 (i) 个奶牛和右侧 (N-i) 个奶牛各选非空子集,红左蓝右或红右蓝左(共 (2 \times (2^i - 1) \times (2^{N-i} - 1)) 种)。
- 水平分隔同理,故总方案为垂直方案 × 2(水平方向)。
2. 线段树状态设计
线段树用于追踪区间内奶牛的队伍分配约束,核心状态:
0:区间内所有奶牛均未被选中(或全为0)。1:区间内所有奶牛均被选中(或全为1)。2:区间内奶牛从0过渡到1(存在0→1的分界)。3:区间内奶牛从1过渡到0(存在1→0的分界)。4:无限制(可任意分配)。
通过
pushup函数合并子区间状态,快速计算整体约束。3. 不合法方案计算
- 对于垂直分隔:当奶牛的 (x) 坐标与 (y) 坐标不完全单调时,存在无法被垂直直线分隔的方案。
- 代码通过动态更新线段树,逐步将 (x \leq i) 的奶牛标记为“强制选中”(类型2),计算此时的不合法方案(状态2和3)。
- 水平方向的不合法方案与垂直方向对称,故最终不合法方案 × 2。
4. 最终答案
通过容斥原理:
答案 = 总方案 - 不合法方案,确保结果模 (10^9+7)。关键难点解析
1. 容斥思想的应用
直接计算合法方案复杂度过高,通过“总可能方案 - 不合法方案”简化问题,将难点转化为计算不合法方案。
2. 线段树状态的高效合并
线段树的5种状态设计精准捕捉了队伍分配的约束,
pushup函数通过组合子区间状态,在 (O(1)) 时间内完成合并,确保整体复杂度 (O(N \log N))。3. 对称性处理
水平和垂直方向的不合法方案具有对称性,代码通过一次计算并 × 2 处理,避免重复实现。
复杂度分析
- 时间复杂度:(O(N \log N)),线段树构建 (O(N)),每次更新和查询 (O(\log N)),共 (O(N \log N))。
- 空间复杂度:(O(N)),线段树大小为 (4 \times N),适配 (N=2 \times 10^5)。
- 1
信息
- ID
- 4404
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者