[BZOJ2603] [POI2003] Motorways

发布时间 2023-11-07 20:20:34作者: JCY_std

本题解思路类似 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\)

  1. \(r_j \le l_i\) 时,此后再也不需要考虑点 \(j\),因此从可并堆中弹出节点 \(j\),然后把新的可并堆的根放入小根堆。

  2. \(l_i < r_j < r_i\) 时,加入树边 \((i, j)\),然后合并 \(j\) 所在的可并堆与 \(i\) 所在的可并堆。

  3. \(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
*/