P3588 PUS 题解

PUS

推销我的洛谷博客。

题意

给出三个整数 \(n,s,m\),请你构造一个整数数组 \(a\) 满足 \(1\leqslant a_i \leqslant 10^9(1\leqslant i \leqslant n)\) 以及 \(m\) 个约束条件,或判断无解。\(a\) 数组中 \(s\) 个数已经给出(保证合法)。

\(m\) 个约束条件格式如下:\(l,r,k,x_1,x_2\cdots x_k\),表示对于 \(\forall i,l\leqslant i \leqslant r,i \not\in x\),都有 \(a_j > a_i(j\in x)\)

如果存在一个满足要求的数组 \(a\),输出 TAK,然后在下一行输出任意一种满足要求的 \(a\);否则输出 NIE

数据范围

  • \(1\leqslant s\leqslant n \leqslant 10^5, 1\leqslant m \leqslant 2 \times 10^5\)
  • \(1\leqslant l < r \leqslant n, 1\leqslant k \leqslant r - l\)
  • \(l \leqslant x_1 < x_2 < \cdots < x_k \leqslant r\)
  • \(\sum k \leqslant 3 \times 10^5\)

思路

线段树优化建图。

初步思路:将约束条件转为建图

题目要求构造一个合法的 \(a\) 数组,可以很显然的发现,当你从大到小的确定 \(a\) 中的元素时,\(a_i\) 越大越好。

那么我们可以根据约束条件,建出一张由较大元素连向较小元素的有向图,最后根据这张图即可构造 \(a\) 数组(用 \(mi_i\) 表示所有连向它的元素的 \(a_{j}\) 最小值,那么 \(a_i\) 最大就为 \(mi_i - 1\))。

如果是暴力建图,我们需要将每个 \(x\) 中的元素都连向其他在 \([l,r]\) 中却不在 \(x\) 中的元素,很明显边的数量是 \(n^2\) 级别的,无法接受。

建图的优化:集中点

如果做过 abc270_f Transportation 的话,你可以想到对于每个约束条件都建一个集中节点 \(id\),将每个在 \(x\) 中的元素都连向 \(id\),再将 \(id\) 连向每个在 \([l,r]\) 中却不在 \(x\) 中的元素,边的数量便变成了 \(O(m \times n + \sum k)\),似乎更差劲了。

别着急,这都是为了为下一步优化做铺垫。

注意有一个细节,题目中约束条件是严格大于,所以 \(a_i = mi_i - 1\)(参考上方),但对于这些集中点来说,他们并不用严格小于连向它的元素,即 \(mi_i=a_i\),需要特别注意。

建图再次优化:线段树优化建图

可以发现,\(id\) 暴力连向每个在 \([l,r]\) 中却不在 \(x\) 中的元素的复杂度过大,但是我们能发现,与其使用暴力连单点,我们不如转换为连接区间,这样就可以使用线段树优化建图。

由于题目保证 \(\sum k \leqslant 3 \times 10^5\),可以发现区间数量也是这个级别(只用考虑相邻两个 \(x\) 之间的区间 \((x_i, x_{i+1})\)),那么就好办了。

附:线段树优化建图的模板

最终:整理答案和判断无解

这个很简单,使用拓扑排序即可解决,注意上面所说的细节。

无解情况如下:

  • 在最优秀的构造方案中,你的 \(a_i\) 肯定是尽量越大越好,所以当你发现某个 \(a_i < 1\),那么必然是无解的。

  • 如果你发现最初已经给定了某个元素 \(a_i\) 并且 \(mi_i - 1 < a_i\),那么也是不合法的。

  • 如果出现了环,那么也是不合法的。

那么本题就结束了,还不理解就看代码。

复杂度

  • 时间:\(O(n+m+\sum k \log n)\)
  • 空间:\(O(n + m)\)

Code

点击查看代码
#include <bits/stdc++.h>

using namespace std;
using ll = long long;

const int N = 1e5 + 10, M = 2e5;

// ls 和 rs 用于记录线段树左右儿子,a 用于记录方案,b 是上文所说的 x,cnb 用于拓扑排序,mi 见上文
int n, m, s, rt, ncnt, ls[N * 3 + M], rs[N * 3 + M], a[3 * N + M], b[N], cnb[3 * N + M], mi[3 * N + M];
vector<int> g[N * 3 + M];
queue<int> q;

//----------------------------------------------------------------- 线段树优化建图
void Add_edge (int x, int y) {
  g[y].push_back(x), cnb[x]++;
}

void build (int id, int l, int r) {
  if (l == r) {
    ls[id] = l;
    Add_edge(l, id);
    return ;
  }
  int mid = (l + r) >> 1;
  ls[id] = ++ncnt, rs[id] = ++ncnt;
  build(ls[id], l, mid), build(rs[id], mid + 1, r);
  Add_edge(ls[id], id), Add_edge(rs[id], id);
}

void modify (int id, int l, int r, int x, int y, int u) {
  if (x <= l && r <= y) {
    Add_edge(id, u);
    return ;
  }
  if (l > y || r < x)
    return ;
  int mid = (l + r) >> 1;
  modify(ls[id], l, mid, x, y, u), modify(rs[id], mid + 1, r, x, y, u);
}
//----------------------------------------------------------------- 线段树优化建图

int main () {
  ios::sync_with_stdio(0), cin.tie(0);
  cin >> n >> s >> m, ncnt = n;
  rt = ++ncnt, build(rt, 1, n);
  for (int i = 1, x, y; i <= s; i++)
    cin >> x >> y, a[x] = y; // 最初给定的元素直接赋值即可
  for (int x, k; m--; ) {
    cin >> b[0] >> x >> k, ncnt++, b[0]--;
    for (int i = 1; i <= k; i++) {
      cin >> b[i], g[b[i]].push_back(ncnt), cnb[ncnt]++;
      if (b[i] != b[i - 1] + 1) //只用管相邻两个元素之间的区间
        modify(rt, 1, n, b[i - 1] + 1, b[i] - 1, ncnt);
    }
    if (x != b[k]) // 最后一个区间别漏了
      modify(rt, 1, n, b[k] + 1, x, ncnt);
  }
  for (int i = 1; i <= ncnt; i++) // 初始化为 1e9 + 1 是为了方便下方减一
    mi[i] = 1e9 + 1;
  q.push(rt);
  while (q.size()) {
    int x = q.front();
    q.pop();
    if (a[x]) {// 初始时有数
      if (mi[x] - 1 < a[x]) { // 判断无解情况 #2
        cout << "NIE";
        return 0;
      }
    } else {
      a[x] = mi[x] - (x <= n); // 细节:集中点 a[i] = mi[i]
      if (a[x] <= 0) { // 判断无解情况 #1
        cout << "NIE";
        return 0;
      }
    }
    for (int i : g[x]) { // 拓扑排序都会写吧
      mi[i] = min(mi[i], a[x]), cnb[i]--;
      if (!cnb[i])
        q.push(i);
    }
  }
  for (int i = 1; i <= n; i++)
    if (a[i] <= 0) { // 如果有环的话,肯定有元素没被赋值。判断无解情况 #1
      cout << "NIE";
      return 0;
    }
  cout << "TAK\n";
  for (int i = 1; i <= n; i++)
    cout << a[i] << ' ';
  return 0;
}
posted @ 2024-07-20 13:57  wnsyou  阅读(5)  评论(0编辑  收藏  举报