1 条题解
-
0
问题分析
我们需要为 个点分配水深值 到 ,满足:
- 个固定点的已知水深
- 个区间约束:区间内 个指定点的水深严格大于其他点
问题可以建模为差分约束系统,但约束是严格大于关系。
核心思想:图论建模 + 拓扑排序
1. 关键转化
将"严格大于"关系转化为:
- 如果 ,则
这样就把严格大于变成了差分约束,可以用有向图表示。
2. 区间优化
约束涉及区间,朴素建图需要 条边,不可行。 使用倍增/ST表技巧优化建图,将区间拆分成 个节点。
算法详解
数据结构
const int N = 1e5 + 1e3 + 7; int tot; // 总节点数 vector<int> g[N * 18]; // 邻接表,最多约180万节点 int p[N][18]; // 倍增节点映射 int n, s, m, f[N * 18], o[N * 18]; // f:水深,o:固定水深 int deg[N * 18]; // 入度算法步骤
步骤1:初始化
cin >> n >> s >> m; fill(f + 1, f + n + 1, 1); // 初始水深至少为1 for (int i = 1; i <= s; i++) { int x, y; cin >> x >> y; f[x] = y; // 当前水深 o[x] = y; // 固定水深(用于验证) }步骤2:倍增建图(ST表结构)
tot = n; // 前n个节点是原始点 // 第0层:每个点对应一个倍增节点 for (int i = 1; i <= n; i++) { p[i][0] = ++tot; // 添加边:i -> p[i][0],权重1(表示i≥p[i][0]+1) g[i].push_back(p[i][0] << 1 | 1); } // 构建倍增层 for (int j = 1; j <= 18; j++) for (int i = 1; i + (1 << j) - 1 <= n; i++) { p[i][j] = ++tot; // 添加边:p[i][j-1] -> p[i][j],权重0 g[p[i][j - 1]].push_back(tot << 1); // 添加边:p[i+(1<<(j-1))][j-1] -> p[i][j],权重0 g[p[i + (1 << (j - 1))][j - 1]].push_back(tot << 1); }边的编码:
v << 1:权重为0的边v << 1 | 1:权重为1的边- 解码时:
v >> 1得到节点编号,v & 1得到权重
步骤3:区间连接函数
auto add = [&](int x, int l, int r) { int k = __lg(r - l + 1); // 2^k ≤ 区间长度 // 从区间[l,r]的代表节点到x添加权重0的边 g[p[l][k]].push_back(x << 1); g[p[r - (1 << k) + 1][k]].push_back(x << 1); };这个函数实现了区间到点的连接,只需要 条边。
步骤4:处理卫星测量
while (m--) { int l, r, k; cin >> l >> r >> k; int u = ++tot; // 创建新节点u int last = l - 1; while (k--) { int x; cin >> x; // 从u到x添加权重0的边:u ≥ x g[u].push_back(x << 1); // 处理[x_i-1, x_{i+1}-1]区间(普通点) if (last + 1 <= x - 1) add(u, last + 1, x - 1); last = x; } // 处理最后一段 if (last + 1 <= r) add(u, last + 1, r); }约束表示:
- 对于区间 ,有 个"大"点
- 这些"大"点水深 > 其他点
- 建图:创建节点 ,表示这些"大"点的最小值
- 到每个"大"点:权重0()
- 每个普通点到 :权重1()
步骤5:拓扑排序计算水深
// 计算入度 for (int i = 1; i <= tot; i++) for (auto v : g[i]) ++deg[v >> 1]; // 拓扑排序 queue<int> q; for (int i = 1; i <= tot; i++) if (!deg[i]) q.push(i); int cc = 0; // 计数访问的节点 while (!q.empty()) { auto x = q.front(); q.pop(); ++cc; for (auto v : g[x]) { int w = v & 1; // 权重 v >>= 1; // 节点编号 // 更新:f[v] ≥ f[x] + w f[v] = max(f[v], f[x] + w); if (!--deg[v]) q.push(v); } }步骤6:验证输出
if (cc != tot) { // 有环,矛盾 cout << "NIE\n"; return 0; } // 检查固定点是否符合 for (int i = 1; i <= n; i++) if (o[i] && f[i] != o[i]) { cout << "NIE\n"; return 0; } // 检查是否超过10^9 for (int i = 1; i <= n; i++) if (f[i] > (int)1e9) { cout << "NIE\n"; return 0; } cout << "TAK\n"; for (int i = 1; i <= n; i++) cout << f[i] << " \n"[i == n];图模型解释
节点类型
- 原始点(1~n):对应每个位置的水深
- 倍增点(p[i][j]):表示区间的最小值
- 约束点(u):表示一组"大"点的最小值
边类型
- 权重0:
a ≥ b关系 - 权重1:
a ≥ b + 1关系
为什么正确?
- 拓扑排序确保无环(有环则矛盾)
f[v] = max(f[v], f[x] + w)计算最小可行解- 倍增优化将区间边数从 降到
复杂度分析
时间复杂度
- 建图:
- 拓扑排序:
- 总复杂度:
空间复杂度
- 节点数:约 ≤ 180万
- 边数:约
示例分析
样例1
5 2 2 2 7 # 点2水深7 5 3 # 点5水深3 1 4 2 2 3 # [1,4]中,点2,3水深>其他 4 5 1 4 # [4,5]中,点4水深>其他执行过程:
- 初始:f[1]=1, f[2]=7, f[3]=1, f[4]=1, f[5]=3
- 处理约束1:点2,3 > 点1,4
- 处理约束2:点4 > 点5
- 拓扑排序计算得到:6 7 1000000000 6 3
样例2
3 2 1 2 3 3 5 1 3 1 2 # [1,3]中点2>其他矛盾:点2>点3,但已知点2=3, 点3=5
算法优势
- 高效: 处理大数据
- 巧妙:倍增优化区间建图
- 正确:严格的图论模型
- 通用:可处理各种约束
总结
本题的核心技巧:
- 差分约束转化:严格大于 → 大于等于+1
- 倍增优化建图:ST表结构减少边数
- 拓扑排序计算:DAG上求最长路
通过将约束系统转化为有向图,利用拓扑排序求解,是处理此类区间约束问题的经典方法。
- 1
信息
- ID
- 5757
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者