BZOJ2603 [POI2003] Motorways
本题解思路类似 kczno1 在 [POI2010] KOL-Railway 的题解。
如果 \(l_i < l_j < r_i < r_j\) 则连边 \((i, j)\),题目转化为判断该图是否是二分图,如果是则给出染色方案。
不妨先找出一个生成森林,然后染色并判断所有同颜色的点是否没有边相连。
把所有 \((l_i, r_i)\) 按 \(l\) 从小到大排序,当 \(l\) 相等时按 \(r\) 从大到小排序。
这样排序后,如果 \((l_i, r_i)\) 在 \((l_j, r_j)\) 之前,则它们有边相连等价于 \(l_j < r_i < r_j\)。
按该顺序依次加入 \((l_i, r_i)\),不断合并连通块同时加入树边。
一个暴力的算法是遍历所有节点 \(j\),如果 \(i\) 不属于 \(j\) 所在的连通块,且 \(l_i < r_j < r_i\),那么加入树边 \((i, j)\)。
观察到 \(l_i\) 单调不降,即如果某一时刻 \(r_j \le l_i\) 则往后也不会有新加入的节点与点 \(j\) 连树边。
用可并堆维护每个连通块中 \(r\) 最小的节点,把所有这些节点按 \(r\) 作为关键字放入小根堆中。
不断弹出小根堆中的节点,记其为 \(j\)。
-
当 \(r_j \le l_i\) 时,此后再也不需要考虑点 \(j\),因此从可并堆中弹出节点 \(j\),然后把新的可并堆的根放入小根堆。
-
当 \(l_i < r_j < r_i\) 时,加入树边 \((i, j)\),然后合并 \(j\) 所在的可并堆与 \(i\) 所在的可并堆。
-
当 \(r_j \ge r_i\) 时,剩余的节点都不会与 \(i\) 连树边,结束合并过程。
容易分析该过程总时间复杂度 \(O(n \log n)\)。
找出生成森林后判断同色点是否没有边相连,照上文方法排序后用树状数组检验即可。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ull = unsigned long long;
using ld = long double;
using i128 = __int128;
using u128 = unsigned __int128;
template <typename T>
void chkmax(T &x, const T &y) {
if (x < y) x = y;
}
template <typename T>
void chkmin(T &x, const T &y) {
if (y < x) x = y;
}
constexpr int MAXN = 2e4 + 10;
int n, m, ord[MAXN], col[MAXN];
pair<int, int> pth[MAXN];
vector<int> g[MAXN];
namespace lt {
struct node {
int ls, rs, dis;
};
node tr[MAXN];
int merge(int u, int v) {
if (!u || !v) return u | v;
if (pth[u].second > pth[v].second) swap(u, v);
tr[u].rs = merge(tr[u].rs, v);
if (tr[tr[u].ls].dis < tr[tr[u].rs].dis) swap(tr[u].ls, tr[u].rs);
tr[u].dis = tr[tr[u].rs].dis + 1;
return u;
}
int pop(int u) { return merge(tr[u].ls, tr[u].rs); }
} // namespace lt
struct cmp {
bool operator()(int x, int y) const { return pth[x].second > pth[y].second; }
};
priority_queue<int, vector<int>, cmp> pq;
void dfs(int u) {
for (auto v : g[u]) {
if (col[v] == -1) {
col[v] = !col[u];
dfs(v);
}
}
}
struct fenwick_tree {
int bit[MAXN];
void update(int p, int x) {
for (; p <= n; p += p & -p) bit[p] += x;
}
int query(int p) {
int ret = 0;
for (; p >= 1; p -= p & -p) ret += bit[p];
return ret;
}
} bit[2];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
lt::tr[0].dis = -1;
cin >> n >> m;
for (int i = 1; i <= m; ++i) cin >> pth[i].first >> pth[i].second;
iota(ord + 1, ord + m + 1, 1);
sort(ord + 1, ord + m + 1, [](int u, int v) {
return pth[u].first < pth[v].first ||
(pth[u].first == pth[v].first && pth[u].second > pth[v].second);
});
for (int i = 1, u, l, r; i <= m; ++i) {
u = ord[i];
int rt = u;
tie(l, r) = pth[u];
while (!pq.empty() && pth[pq.top()].second < r) {
int t = pq.top();
pq.pop();
while (t && pth[t].second <= l) t = lt::pop(t);
if (!t) continue;
if (pth[t].second < r) {
g[u].emplace_back(t);
g[t].emplace_back(u);
rt = lt::merge(rt, t);
} else {
pq.emplace(t);
}
}
pq.emplace(rt);
}
fill(col + 1, col + m + 1, -1);
for (int i = 1, u, l, r; i <= m; ++i) {
u = ord[i];
tie(l, r) = pth[u];
if (col[u] == -1) {
col[u] = 0;
dfs(u);
}
if (bit[col[u]].query(r - 1) != bit[col[u]].query(l)) {
cout << "NIE\n";
return 0;
}
bit[col[u]].update(r, 1);
}
for (int i = 1; i <= m; ++i) cout << "NS"[col[i]] << "\n";
return 0;
}
/*
g++ MOT.cpp -o MOT -std=c++14 -O2 -Wall -Wextra -Wshadow -g
-fsanitize=address,undefined
*/