• 【树状数组】【P5069】[Ynoi2015]纵使日薄西山


    Description

    给定一个长度为 (n) 的非负整数序列 ({a_n})(q) 次操作,每次要么单点修改序列某个值,要么查询整个序列需要操作多少次才能变成全 (0)

    一次操作是指:找到序列的最大值的位置,如果有多个最大值则取最左边的,然后将这个数和这个位置左右紧挨着的数都 (-1),如果减到 (0) 则不减。

    Limitation

    (1~leq~n,~q~leq~10^5)

    序列值域在 (10^9) 范围内。

    Solution

    第一次写 YnOI 体验极差

    首先注意到的是如果确定要操作某个位置 (x) 了,那么最终这个位置一定被操作了 (a_x) 次。原因是这个位置每操作一次,左右都会减一,这样这个位置永远不会因为要操作左右而减小,所以让这个位置变成 (0) 的唯一可能只有操作这个位置本身。于是找到一个位置将它一直操作到 (0) 与原操作方式是等价的。于是问题被转化成了求所有会被操作位置的数字和。

    先设序列中的数互不相同。考虑序列中一个单调递增或者单调递减的极长子序列,首先会操作最大值,减到 (0) 以后次大值一定也被减到了 (0),然后再操作次次大值,然后次次次大也减到 (0) 了……一直这么下去发现所有被操作位置的奇偶性是相同的,即在这个极长单调序列中所有位置与最大值位置的奇偶性相同的点都会被操作。考虑用 set 维护序列中所有极值点,用两个树状数组分别奇数下标的前缀和和偶数下标的前缀和,即可求出初始序列的操作次数。

    考虑修改的时候,只需要继续修改序列的极值点即可。

    于是你就要面对这个点本来是极值点,本来不是极值点。他左边是极值点,右边是极值点,都不是极值点,改完以后极值位置不变,改完以后新增一个极大值,新增一个极小值等等十几种情况

    考虑手动讨论每种情况显然非常不靠谱,但是注意到修改某个位置最多只会影响到这个位置向左右分别数两个极值点(不包括自身)这段区间的情况,同时新可能增加或者删除的极值点只有这个位置和这个位置左右各一个位置共三个位置。于是可以暴力重构这段区间的答案,由于求答案的时候只需要扫描区间内所有极值点,而极值点个数又是常数级别的,于是可以 (O(log n)) 的去修改。这个 (log) 是由于 setlower_bound 造成的。

    解释一下为什么会影响到左右数第二个极值点,如下数据:

    9 2 6 8
    

    三个极值点分别是 (1,2,4),其中 (2) 是极小值,(1,4) 是极大值。如果将位置 (1) 修改为 (1),那么极值点变成了 (1,4),由此,([2,4]) 这段区间不复存在,变成了 ([1,4]) 这段区间,需要重新统计,影响到了 (1) 向右数第 (2) 个极值点 (4)

    考虑如果序列中有相同的数怎么办:

    如果相同的数不连续,那么根本不用管。如果连续,先假设这个这组数的第一个数大于左侧的数,那么操作到这组数的时候,这个数会被最先操作,于是这组数的第一个位置应该被设为极大值。如果这组数的第一个数小于左侧的数,那么第一个数是否被操作不由这个数决定,它不应该成为极小值。

    考虑这组数的最后一个数,如果它大于右侧的数,那么这个数操作完以后会继续操作右侧的数,它不应该成为极大值。如果它小于左侧的数,那么左右两侧的区间都会在操作到这个位置的时候停止,那么这个位置应该成为极小值。

    有一些细节:

    考虑一个极小值会被操作,当且仅当它左右的数都没有被操作,那么它不会被减掉,只能自己单独操作。这种情况即它到左右的极大值点的距离都是偶数。

    在扫描区间答案的时候,如果区间有 (x) 个极值点,那么只需要考虑 (x-1) 段区间的答案,因此只需要扫描 (x-1) 个极值点。但是需要注意到的是不能漏掉剩下那个极值点是极小值的讨论。

    Code

    参考了 @FlushHu 神仙的代码,在此表示感谢。

    #include <cstdio>
    #include <set>
    #ifdef ONLINE_JUDGE
    #define freopen(a, b, c)
    #endif
    
    typedef long long int ll;
    
    namespace IPT {
      const int L = 1000000;
      char buf[L], *front=buf, *end=buf;
      char GetChar() {
        if (front == end) {
          end = buf + fread(front = buf, 1, L, stdin);
          if (front == end) return -1;
        }
        return *(front++);
      }
    }
    
    template <typename T>
    inline void qr(T &x) {
      char ch = IPT::GetChar(), lst = ' ';
      while ((ch > '9') || (ch < '0')) lst = ch, ch=IPT::GetChar();
      while ((ch >= '0') && (ch <= '9')) x = (x << 1) + (x << 3) + (ch ^ 48), ch = IPT::GetChar();
      if (lst == '-') x = -x;
    }
    
    namespace OPT {
      char buf[120];
    }
    
    template <typename T>
    inline void qw(T x, const char aft, const bool pt) {
      if (x < 0) {x = -x, putchar('-');}
      int top=0;
      do {OPT::buf[++top] = static_cast<char>(x % 10 + '0');} while (x /= 10);
      while (top) putchar(OPT::buf[top--]);
      if (pt) putchar(aft);
    }
    
    const int maxn = 100005;
    
    int n, dn;
    ll ans;
    ll MU[maxn], odd[maxn], eve[maxn];
    std::set<int>s;
    
    void check(const int x);
    void upd(const int x);
    void calc(const int ll, const int rr, const int v);
    
    int main() {
      freopen("1.in", "r", stdin);
      qr(n); dn = n + 1;
      s.insert(0); s.insert(n + 1);
      for (int i = 1; i <= n; ++i) {
        upd(i);
      }
      int q = 0, x;
      qr(q);
      while (q--) {
        x = 0; qr(x);
        upd(x);
        printf("%lld
    ", ans);
      }
      return 0;
    }
    
    inline int lowbit(const int x) { return x & -x; }
    
    void update(ll *const a, int p, const ll v) {
      while (p <= dn) {
        a[p] += v;
        p += lowbit(p);
      }
    }
    
    ll query(ll *const a, int p) {
      ll _ret = a[p];
      do _ret += a[p -= lowbit(p)]; while (p);
      return _ret;
    }
    
    void upd(const int x) {
      ll y = 0; qr(y);
      auto v = s.lower_bound(x);
      auto l = v, r = v;
      --l; ++r;
      if (l != s.begin()) --l;
      if ((r != s.end()) && (*v == x)) ++r;
      if (r == s.end()) --r;
      calc(*l, *r, -1);
      update(x & 1 ? odd : eve, x, y - MU[x]); MU[x] = y;
      check(x);
      if (x != 1) check(x - 1);
      if (x != n) check(x + 1);
      calc(*l, *r, 1);
    }
    
    void check(const int x) {
      if ((MU[x] > MU[x - 1]) != (MU[x] < MU[x + 1])) s.insert(x);
      else s.erase(x);
    }
    
    void calc(const int ll, const int rr, const int v) {
      auto l = s.lower_bound(ll), r = s.lower_bound(rr);
      while (r != l) {
        auto t = r; --t;
        if (MU[*t] < MU[*r]) {
          auto ad = *r & 1 ? odd : eve;
          ans += v * (query(ad, *r) - query(ad, *t));
        } else {
          auto ad = *t & 1 ? odd : eve;
          ans += v * (query(ad, *r - 1) - query(ad, *t));
          auto l0 = r, r0 = r;
          if (l0 != s.end()) --l0;
          ++r0; if (r0 == s.end()) --r0;
          if ((!((*r - *l0) & 1)) && (!((*r0 - *r) & 1))) {
            ans += MU[*r] * v;
          }
        }
        --r;
      }
      if (MU[*l] >= MU[*l + 1]) return;
      auto l0 = r, r0 = r;
      if (l0 != s.end()) --l0;
      ++r0; if (r0 == s.end()) --r0;
      if ((!((*r - *l0) & 1)) && (!((*r0 - *r) & 1))) {
        ans += MU[*r] * v;
      }
    }
    
  • 相关阅读:
    C语言高效编程的几招
    部分功能函数命名总结(补充中)
    Linux的多线程编程的高效开发经验
    生成PDF下载 HTTP或FTP远程获取PDF
    .dnn文件 xml 配置
    有身体 有钱 还要有什么能进太空?
    dnn 4.8.2 RegisterStartupScript 函数失效 解决方案
    字符串 分隔符 逗号行吗? JAVASCRIPT 函数参数中有单双引号
    2007年的最后一天,最后一个小时。
    标题中有单引号',怎么查询?
  • 原文地址:https://www.cnblogs.com/yifusuyi/p/11108521.html
Copyright © 2020-2023  润新知