ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

P4587 神秘数 Sol

2022-09-05 15:01:30  阅读:128  来源: 互联网

标签:神秘 pre 凑出 前缀 int res ll P4587 Sol


主席树好题。

本质上是对于前缀的理解与转化。

同题见牛牛的凑数游戏


实际上那场比赛 T2 和 T3 都是前缀相关的题目。

这题是 T3,看到很容易想到二进制拆分。

稍微推广一下可以发现,对于从小到大的一系列数 \(a_1,a_2,...,a_n\),处理前缀和 \(pre_1,pre_2,...,pre_n\);如果存在一个前缀和 \(pre_{i-1}+1<a_i\),那么无解。

这一点是很好理解的。

本来有一个 \(pre_{i-1}\)(这里满足 \([0,pre_{i-1}]\) 都能被凑出),那么这个 \(a_i\) 的作用就是将 \(pre_{i-1}\) 在数轴上平移了 \(a_i\) 个单位。例如,就是把 \([0,pre_{i-1}]\) 扩展为 \([0,pre_{i-1}]\space\cup\space[a_i,pre_{i-1}+a_i]\)。

这样就很好理解了,存在 \(pre_{i-1}+1<a_i\) 时必定无解。

接下来就可以想,如何使得数组有序?

第一种想法是开一个 multiset 存放当前所有元素。

然后使用莫队算法进行优化。

判断无解只需要开一个线段树,将加入 / 删除元素的过程转化为前缀和上的加减,然后判断区间 \(\min\) 即可。但是复杂度是 \(N \sqrt{N} \log N\) 级别的,无法接受。

好吧,既然不能用 multiset,再想想怎么快速排序求答案?

想到桶排。接着发现加入点的过程长得特别像值域线段树加点。。。

这个问题解决了,那么又如何维护前缀呢?

想到主席树,对于每一个前缀维护一个根就好了。

区间操作消掉左端点以前的影响即可。

转化到这一步,又如何优化这个跑前缀的过程呢?

其实想到整个区间的元素都已经处理到一起了,假设 \([1,p]\) 内元素能凑出的最大元素为 \(res\)。既然 \(res\) 能被凑出,那么可以考虑 \([1,res]\) 内元素凑出的最大元素。如果不满足前面给出的 \(pre_{i-1}+1<a_i\) 条件,就可以直接返回 \(res+1\)。这样的过程类似于倍增,复杂度显然为 \(\log\)。

那么这道题就做完了,复杂度 \(N \log^2 N\)

solve 函数内 if (cur > INF) 是因为牛客上没有和 \(\le 10^9\) 的限制。

#include <bits/stdc++.h>
using ll = long long;
using namespace std;

const int N = 1e5 + 10, INF = 1e9;
int n, m, cnt, a[N], rot[N]; ll sum[N];
struct Node { int ls, rs; ll val; } t[N << 5];

inline void ins(int &rt, int f, int l, int r, int pos, int val) {
  rt = ++cnt; t[rt] = t[f], t[rt].val += val;
  if (l == r) return ; int mid = (l + r) >> 1;
  if (pos <= mid) ins(t[rt].ls, t[f].ls, l, mid, pos, val);
  else ins(t[rt].rs, t[f].rs, mid + 1, r, pos, val);
  return ;
}

inline ll query(int rt, int f, int l, int r, int L, int R) {
  if (L <= l && r <= R) return t[f].val - t[rt].val;
  int mid = (l + r) >> 1; ll res = 0;
  if (L <= mid) res += query(t[rt].ls, t[f].ls, l, mid, L, R);
  if (R > mid) res += query(t[rt].rs, t[f].rs, mid + 1, r, L, R);
  return res;
}

inline void solve() {
  int l, r; cin >> l >> r; ll res = 1;
  while (1) {
    ll cur = query(rot[l - 1], rot[r], 1, INF, 1, (int)res);
    // if (cur > INF) { cout << sum[r] - sum[l - 1] + 1 << endl; return ; }
    if (cur >= res) res = cur + 1; else break;
  }
  return cout << res << endl, void();
}

int main() {
  ios_base::sync_with_stdio(false); cin.tie(0), cout.tie(0);
  cin >> n >> m; for (int i = 1; i <= n; ++i) cin >> a[i], sum[i] = sum[i - 1] + a[i];
  for (int i = 1; i <= n; ++i) ins(rot[i], rot[i - 1], 1, INF, a[i], a[i]);
  while (m--) solve();
  return 0;
}

标签:神秘,pre,凑出,前缀,int,res,ll,P4587,Sol
来源: https://www.cnblogs.com/MistZero/p/P4587-Sol.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有