首页 > 其他分享 >[CEOI2023] Tricks of the Trade 题解

[CEOI2023] Tricks of the Trade 题解

时间:2024-11-15 16:41:26浏览次数:1  
标签:cnt rs int 题解 Tricks mid kMaxN CEOI2023 kMaxT

Description

有 \(n\) 个机器人排成一排,第 \(i\) 个机器人的购买价是 \(a_i\) 欧元,卖出价是 \(b_i\) 欧元。

给定 \(1\le k\le n\),你需要购买一段长度至少为 \(k\) 的区间中所有的机器人,然后选择其中的恰好 \(k\) 个机器人来卖出。

你需要求出:

  1. 你能够得到的最大收益;
  2. 在收益最大化的前提下,哪些机器人可以在某种最优方案中被卖出。

\(1\leq k\leq n\leq 2.5\times 10^5\)。

Solution

先考虑第一问怎么求。

不妨设 \(f(l,r)\) 表示 \([l,r]\) 这个区间的收益,即为 \(b\) 数组在 \([l,r]\) 的前 \(k\) 大的和减去 \(a\) 数组在 \([l,r]\) 的区间和。

打表一下会发现 \(f(l,r)\) 满足四边形不等式 \(f(a,c)+f(b,d)\geq f(a,d)+f(b,c)\)。

证明

容易发现 \(a\) 数组的区间和没什么意义,先扔掉,设 \(g(l,r)\) 表示 \(b\) 数组在 \([l,r]\) 前 \(k\) 大的和,那么转化为要证明 \(g(l,r)+g(l+1,r+1)\geq g(l,r+1)+g(l+1,r)\)。

先把 \(g(l+1,r)\) 拿出来,\(g(l,r+1)\) 相当于是在 \([l+1,r]\) 同时加入 \(b_l,b_{r+1}\) 两个数去更新第 \(k\) 大和第 \(k-1\) 大,而 \(g(l,r)+g(l+1,r+1)\) 则是分别加入 \(b_l\) 和 \(b_{r+1}\) 去更新 \([l+1,r]\) 第 \(k\) 大,这个显然比两个去更新第 \(k\) 大和第 \(k-1\) 大优。

所以第一问直接决策单调性分治即可。

对于第二问,先把所有收益等于最大值的区间 \([l,r]\) 拿出来,那么 \([l,r]\) 内 \(b_i\) 不小于区间第 \(k\) 大的位置都会被标记。

但是这样的最优区间可能是 \(O(n^2)\) 级别的,暴力找是做不了的。

考虑如果存在两个最优区间 \([l_1,r_1],[l_2,r_2]\),满足 \(l_1\leq l_2\leq r_2\leq r_1\),由于 \(f(l_1,r_2)+f(l_2,r_1)\geq f(l_1,r_2)+f(l_2,r_1)=2\times ans\),所以 \([l_1,r_2]\) 和 \([l_2,r_1]\) 也是最优区间,这两个区间加上 \([l_2,r_2]\) 可以覆盖任何 \([l_1,r_1]\) 可以覆盖的位置,所以只需要保留 \([l_1,r_2],[l_2,r_2],[l_2,r_1]\) 即可。

于是剩下的区间是个类似双指针的东西,只有 \(O(n)\) 个,所以在决策单调性分治的时候求出使得每个右端点 \(r\) 收益最大的最大左端点 \(l\),后面找区间的时候双指针扫即可。

时间复杂度:\(O(n\log^2 n)\)。

Code

#include <bits/stdc++.h>

// #define int int64_t

const int kMaxN = 2.5e5 + 5, kMaxT = kMaxN * 35;

int n, k;
int a[kMaxN], b[kMaxN];
int sgt_tot, rt[kMaxN], ls[kMaxT], rs[kMaxT], cnt[kMaxT];
int64_t ans, suma[kMaxT], sumb[kMaxT], f[kMaxT], p[kMaxT];
bool op[kMaxN];
std::vector<std::pair<int, int>> seg;

int update(int x, int l, int r, int ql, int v) {
  int nz = ++sgt_tot;
  ls[nz] = ls[x], rs[nz] = rs[x], cnt[nz] = cnt[x] + v, sumb[nz] = sumb[x] + v * ql;
  if (l == r) return nz;
  int mid = (l + r) >> 1;
  if (ql <= mid) ls[nz] = update(ls[x], l, mid, ql, v);
  else rs[nz] = update(rs[x], mid + 1, r, ql, v);
  return nz;
}

int64_t query(int x, int y, int l, int r, int k) {
  if (k <= 0 || cnt[y] - cnt[x] == 0) return 0;
  if (l == r) return l * k;
  int mid = (l + r) >> 1, crs = cnt[rs[y]] - cnt[rs[x]];
  if (k <= crs) return query(rs[x], rs[y], mid + 1, r, k);
  else return sumb[rs[y]] - sumb[rs[x]] + query(ls[x], ls[y], l, mid, k - crs);
}

int getkth(int x, int y, int l, int r, int k) {
  if (l == r) return l;
  int mid = (l + r) >> 1, crs = cnt[rs[y]] - cnt[rs[x]];
  if (k <= crs) return getkth(rs[x], rs[y], mid + 1, r, k);
  else return getkth(ls[x], ls[y], l, mid, k - crs);
}

int64_t calc(int l, int r) {
  if (r - l + 1 < k) return -1e18;
  return query(rt[l - 1], rt[r], 1, 1e9, k) - (suma[r] - suma[l - 1]);
}

void prework() {
  for (int i = 1; i <= n; ++i) {
    rt[i] = update(rt[i - 1], 1, 1e9, b[i], 1);
    suma[i] = suma[i - 1] + a[i];
  }
}

void solve(int l, int r, int L, int R) {
  if (l > r) return;
  int mid = (l + r) >> 1;
  f[mid] = calc(L, mid), p[mid] = L;
  for (int i = L + 1; i <= R; ++i) {
    int64_t val = calc(i, mid);
    if (val >= f[mid]) {
      f[mid] = val, p[mid] = i;
    }
  }
  solve(l, mid - 1, L, p[mid]), solve(mid + 1, r, p[mid], R);
}

void getseg() {
  for (int i = k, j = 1; i <= n; ++i) {
    if (f[i] != ans) continue;
    for (;; ++j) {
      if (calc(j, i) == ans) seg.emplace_back(j, i);
      if (j == p[i]) break;
    }
  }
}

void work() {
  static std::vector<std::pair<int, int>> vec[kMaxN];
  for (auto [l, r] : seg) {
    int kth = getkth(rt[l - 1], rt[r], 1, 1e9, k);
    vec[l].emplace_back(kth, 1), vec[r + 1].emplace_back(kth, -1);
  }
  std::multiset<int> st;
  for (int i = 1; i <= n; ++i) {
    for (auto [x, v] : vec[i]) {
      if (v == 1) st.emplace(x);
      else st.erase(st.lower_bound(x));
    }
    if (st.size() && b[i] >= *st.begin()) op[i] = 1;
  }
}

void dickdreamer() {
  std::cin >> n >> k;
  for (int i = 1; i <= n; ++i) std::cin >> a[i];
  for (int i = 1; i <= n; ++i) std::cin >> b[i];
  prework();
  memset(f, 0xcf, sizeof(f));
  solve(k, n, 1, n);
  ans = *std::max_element(f + 1, f + 1 + n);
  getseg(), work();
  std::cout << ans << '\n';
  for (int i = 1; i <= n; ++i) std::cout << op[i];
}

int32_t main() {
#ifdef ORZXKR
  freopen("in.txt", "r", stdin);
  freopen("out.txt", "w", stdout);
#endif
  std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
  int T = 1;
  // std::cin >> T;
  while (T--) dickdreamer();
  // std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
  return 0;
}

标签:cnt,rs,int,题解,Tricks,mid,kMaxN,CEOI2023,kMaxT
From: https://www.cnblogs.com/Scarab/p/18548247

相关文章

  • P11276 第一首歌 题解
    P11276第一首歌题解一道简单的字符串题目。题目解释说求一个最短的字符串\(t\),使其满足对于给定的字符串\(s\):\(s\)为\(t\)的前缀。\(s\)为\(t\)的后缀。\(s\)不为\(t\)。仔细思考一下,则易得\(t\)的最短长度的最大值为\(s\)的两倍。即当\(s\)......
  • 读数据质量管理:数据可靠性与数据质量问题解决之道04收集与清洗
    1.      收集数据1.1.        数据收集和清洗是生产管道中的第一步1.1.1.          数据转换和测试则在生产管道中解决数据质量问题1.2.        在收集数据时,管道的任何地方可能都没有入口点重要,因为入口点是任何数据管道中最上游的位......
  • 题解:P11277 世界沉睡童话
    比较简单的构造。注意到题面给出\(a_i\le2n-1\)的条件,考虑这个有什么用,你会发现从\(n\)到\(2n-1\)这\(n\)个数都是两两互不为约数,所以当我们构造出序列后,这些数可以用来填补空位。\(k\)的上界是\(\frac{n(n-1)}{2}\),显然在全部都为同一个数的时候取到,显然有\(x\)个......
  • Atcoder ABC 216 G 01Sequence 题解 [ 蓝 ] [ 差分约束 ]
    01Sequence:比较板的差分约束,但有一个很妙的转化。朴素差分约束设\(x_i\)表示第\(i\)位的前缀和。我们要最小化\(1\)的个数,就要求最小解,就要求最长路。因为约束条件都是大于等于号,所以求最长路才能满足所有条件。求最大解也是同理。我们可以对于每一个条件,列出如下不等式......
  • 2024年09月CCF-GESP编程能力等级认证Python编程二级真题解析
    本文收录于专栏《Python等级认证CCF-GESP真题解析》,专栏总目录:点这里,订阅后可阅读专栏内所有文章。一、单选题(每题2分,共30分)第1题据有关资料,山东大学于1972年研制成功DJL-1计算机,并于1973年投入运行,其综合性能居当时全国第三位。DJL-1计算机运算控制部分所使用的......
  • [CF1188E] Problem from Red Panda 题解
    [CF1188E]ProblemfromRedPanda题解考虑每个位置的操作次数\(c_i\),不难发现,\(i\)气球最后的颜色个数\(d_i\)是\(a_i+c_ik-\sumc_i\),如果存在\(\forallc_i>0\),那么我们总是可以把所有气球少操作一次,这样上式不变,不影响最后的序列,下文所有的操作序列都假设\(\min......
  • [JXOI2017] 加法 题解
    [JXOI2017]加法最小值最大,一眼二分。贪心地,每次尽量对包含当前序列最小值的区间做加法操作,也就是说,对于当前二分的答案\(x\),任何的\(A_i<x\)都需要被操作。从左到右地考虑答案。我们认为当前点之前的所有值都已经满足条件,于是我们只需考虑每次区间对当前点之后答案造成的贡......
  • 题解:P7836 「Wdoi-3」夜雀 collecting
    题解摘自做题记录。分析数据范围明显得要让我们分开搞。【Sub2】应该是暴力。这里有个主体思路,我们完全可以贪心地将当前背包里的食材删掉,直到每种出现过的食材数量刚好为\(1\)。因为我们保留更多的是没有用的。那么我们就可以用二进制数表示\(x\)种食材的出现状态了。同......
  • 【题解】CF1982
    A考虑两队的领先情况改变,那么一定有某一时刻两队的比分相等于是首先检查最开始的领先队伍,再检查现在的领先队伍,如果前后不同,则\(YES\),否则\(NO\)B注意到当\(x=1\),则会进入循环,手模一下发现\(ans=k\%(y-1)+1)\)现在的问题是:什么时候\(x=1\)?直接手动模拟即可,不难证明时......
  • Intellij IDEA如何设置中文版?安装中文汉化包插件?失败问题解决!
    前言大家好,我是小徐啊。IntellijIDEA默认是英文的操作界面,因为是外国人开发的嘛~对于英文好一点的同学来说,英文就英文吧,但对于英文比较差的同学,就还是希望能够汉化一下,变成熟悉的中文。今天小徐就来介绍下如何在IDEA中安装汉化插件,以及在这过程中,我遇到的奇怪问题,以及最后如何......