首页 > 其他分享 >P3588 PUS 题解

P3588 PUS 题解

时间:2024-07-20 14:08:29浏览次数:6  
标签:int 题解 元素 ncnt 建图 P3588 id leqslant 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;
}

标签:int,题解,元素,ncnt,建图,P3588,id,leqslant,PUS
From: https://www.cnblogs.com/wnsyou-blog/p/18313027/P3588_solution

相关文章

  • P9531 题解
    blog。提供一份代码短的题解。一个暴力做法:维护\(w_i<w_{now}\)与\(w_i\gew_{now}\)的前后缀MST,查\(X_i\)时将前后缀MST合并,直接求得答案。考虑一棵\((u_{now},v_{now},X)\)的前缀MST。因为\(w_i<X\)时\(w_i\)越大\(\midX-w_i\mid\)越小,所以按边权从大到小......
  • [ARC173E] Rearrange and Adjacent XOR 题解
    题目链接点击打开链接题目解法太牛了!!!这道题我的第一反应是从高位往低位确定,但这样就全错了换个角度考虑,找最后的答案可以由那些数异或而成这里给出结论:答案一定是偶数个数的异或和(在\(n\%4=2\)且\(n\neq2\)时,不能由全集构成)证明:选出的数非全集的情况用数学归纳法......
  • 题解 Codeforces 1994H Fortnite
    首先第一次询问肯定是问\(\texttt{aa}\),答案减去\(1\)得到基数\(p\)。然后我们随意询问一个真实Hash值(取模之前)\(X\)大于模数\(m\)的字符串,例如\(s=\texttt{zzz}\cdots\texttt{zzz}\)(\(50\)个\(\textttz\))。设它取模得到的Hash值是\(a\)。考虑正整数\(1\leqb......
  • CF466E Information Graph 题解
    题目链接LuoguCodeforces题意简述某公司中有\(n\)名员工。为方便起见,将这些员工从1至\(n\)编号。起初,员工之间相互独立。接下来,会有以下\(m\)次操作:员工\(y\)成为员工\(x\)的上司。保证此前\(x\)没有上司。员工\(x\)拿到一份文件并签字,随后交给他的上司......
  • P3206 城市建设 题解
    Statement动态边权的最小生成树。\(n\le2\cdot10^4,m,q\le5\cdot10^4\)Solution法一:改边权相当于删一条边再加一条边.考虑LCT维护最小生成树,加边好做,但删边比较麻烦,于是采用线段树分治,撤销一条边是好做的,cut再link一下就行了.\(O(n\logn\logq)\),常数较大.法二:两个结......
  • 07-19 题解
    07-19题解B思路实质:有一个完全图,删掉一些边,然后在图上找一棵生成树但是图的边数是\(n^2\)级别的,极其稠密找生成树的步骤:从一个点开始,把与它相连的,不在同一连通块的点连在一起所以我们只要确保每次都能在尽量少的步数内找到一个合法点一共有n个点,确定一个点之......
  • 2024年牛客暑期多校训练营1 A题 A Bit Common题解
    题目的大意:首先,给你一个长度为n的序列A,A序列中每一个元素全都小于2m,并且大于等于0。A序列要满足存在一个非空子序列的与运算(&)和为1;输出这样的A序列有几个,最后对正整数q取模。(1<=n,m<=5000,1<=q<=109)输入只有一行n,m,q,输出包含一个整数。 题解:要满......
  • [NOI2007] 项链工厂 题解
    前言题目链接:洛谷;Hydro&bzoj。题意简述yzh喜欢写DS题!你要维护一个环:顺时针移动\(k\)位;翻转\(2\simn\);交换\(i\)与\(j\);区间覆盖;查询整个环有几个颜色段;查询\(i\simj\)有几个颜色段。题目分析平衡树板子啊,代码很好写,\(273\)行。但是为什么不使用线......
  • 通过pushgateway 推送的一批机器的nodeexporter,怎么判断这批机器是否有宕机的,已解决
    上回说到,即使你的监控已经下线,prometheus还会拉取到旧的监控数据,需要手动清理pushgateway不要的数据。但是这样并不符合我们监控的预期,尤其是对于pushgateway获取的机器如果宕机的话,就会收不到告警,本文针对此问题做一个处理给node-exporter增加一个告警项,unix时间戳,这里采用n......
  • 1004:字符三角形 题解
    题目链接题目描述给定一个字符,用它构造一个底边长\(5\)个字符,高\(3\)个字符的等腰字符三角形。解题思路由于是字符,我们需要定义一个char类型的字符变量。第一行为两个空格和一个字符第二行为一个空格和三个字符第三行是五个字符输出即可ACCode#include<bits/stdc++.h......