Codeforces Round #431 (Div. 2) E. Goodbye Souvenir CDQ分治 或 离散化 线段树套树状数组

链接

http://codeforces.com/contest/849/problem/E

题意

n 个数,q个操作,每个操作要么是把第 p 个数的值改成x,要么是询问 [l,r] 区间的各个值出现的第一个位置和最后一个位置差值的和

思路

自己没想到,看了这个博客后才明白了做法:http://www.cnblogs.com/ditoly/p/CF848C.html

这题一维难以维护的地方是,一个点的权值可能是和不在查询区间的点产生的,于是有把一维问题转换成二维问题的想法,一个点的横坐标就是这个点的位置,纵坐标是这个点前一个与它颜色相同的点的位置,权值是横坐标减纵坐标。
这样查询 [l,r] 区间的答案,就是二维平面上, (l,l) 点和 (r,r) 点组成的矩阵内的权值之和。

考虑没有修改的情况,矩阵求和是一个经典问题,一次扫描线就可以求出答案了,现在有了修改操作,所以利用cdq分治,把动态问题变静态,而后再用扫描线求答案。
cdq分治的过程中,考虑左右子问题的答案都已经求出,现在要考虑左子问题的修改操作对右子问题的查询操作的影响,把左边的修改操作和右边的查询操作放在一起,进行扫描线即可。

另外题解的做法是单纯地用线段树套树状数组的方法模拟整个二维平面,当然这样容易爆空间,所以要把所有操作离线,进行离散化后再重新进行操作。

代码

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <set>

using namespace std;

typedef long long LL;
const int N = 1e5 + 5;

struct Node {
  int k, x, y, v;
  Node(int k = 0, int x = 0, int y = 0, int v = 0):
    k(k), x(x), y(y), v(v) {}
};

struct Event {
  int k, pos, l, r, v;
  Event(int k = 0, int pos = 0, int l = 0, int r = 0, int v = 0):
    k(k), pos(pos), l(l), r(r), v(v) {}
  bool operator<(const Event &rhs) const {
    return pos < rhs.pos || (pos == rhs.pos && k < rhs.k);
  }
};

int n, q;
int base, tot, tol;
int pre[N], a[N];
LL ans[N];
LL seg_tree[1 << 20];
Node nodes[N << 3];
Event events[N << 3];
set<int> col[N];

void add(int x, int v) {
  for (x += base; x; x >>= 1) seg_tree[x] += v;
}

LL query(int l, int r) {
  LL ret = 0;
  l += base - 1; r += base + 1;
  for (; l ^ r ^ 1; l >>= 1, r >>= 1) {
    if (~ l & 1) ret += seg_tree[l ^ 1];
    if (r & 1) ret += seg_tree[r ^ 1];
  }
  return ret;
}

inline void change(int pos, int val) {
  if (a[pos] == val) return ;
  auto it = col[a[pos]].find(pos);
  int pre = -1; int aft = -1;
  if (it != col[a[pos]].begin()) {
    --it; pre = *it; ++it;
    nodes[++tot] = Node(0, pos, pre, -(pos - pre));
  }
  ++it;
  if (it != col[a[pos]].end()) {
    aft = *it;
    nodes[++tot] = Node(0, aft, pos, -(aft - pos));
  }
  if (pre != -1 && aft != -1) {
    nodes[++tot] = Node(0, aft, pre, aft - pre);
  }
  col[a[pos]].erase(pos);
  a[pos] = val;
  it = col[val].insert(pos).first;
  pre = -1; aft = -1;
  if (it != col[val].begin()) {
    --it; pre = *it; ++it;
    nodes[++tot] = Node(0, pos, pre, pos - pre);
  }
  ++it;
  if (it != col[val].end()) {
    aft = *it;
    nodes[++tot] = Node(0, aft, pos, aft - pos);
  }
  if (pre != -1 && aft != -1) {
    nodes[++tot] = Node(0, aft, pre, -(aft - pre));
  }
}

void solve(int l, int r) {
  if (l == r) return ;
  int mid = (l + r) >> 1;
  solve(l, mid); solve(mid + 1, r);
  tol = 0;
  for (int i = l; i <= mid; ++i) if (nodes[i].k == 0) {
    events[tol++] = Event(0, nodes[i].x, nodes[i].y, 0, nodes[i].v);
  }
  for (int i = mid + 1; i <= r; ++i) if (nodes[i].k == 1) {
    events[tol++] = Event(1, nodes[i].x - 1, nodes[i].x, nodes[i].y, nodes[i].v);
    events[tol++] = Event(2, nodes[i].y, nodes[i].x, nodes[i].y, nodes[i].v);
  }
  sort(events, events + tol);
  for (int i = 0; i < tol; ++i) {
    if (events[i].k == 0) {
      add(events[i].l, events[i].v);
    }
    else if (events[i].k == 1) {
      ans[events[i].v] -= query(events[i].l, events[i].r);
    }
    else {
      ans[events[i].v] += query(events[i].l, events[i].r);
    }
  }
  for (int i = 0; i < tol; ++i) if (events[i].k == 0) {
    add(events[i].l, -events[i].v);
  }
}

int main() {
  while (~scanf("%d%d", &n, &q)) {
    for (base = 1; base <= n + 1; base <<= 1);
    for (int i = 0; i <= base << 1; ++i) seg_tree[i] = 0;
    tot = 0;
    int x, l, r;
    for (int i = 1; i <= n; ++i) {
      pre[i] = 1;
      col[i].clear();
    }
    for (int i = 2; i <= n + 1; ++i) {
      scanf("%d", &x);
      nodes[++tot] = Node(0, i, pre[x], i - pre[x]);
      pre[x] = i;
      col[x].insert(i);
      a[i] = x;
    }
    for (int i = 1; i <= q; ++i) {
      scanf("%d%d%d", &x, &l, &r);
      if (x == 1) {
        change(l + 1, r);
        ans[i] = -1;
      }
      else {
        ++l; ++r;
        nodes[++tot] = Node(1, l, r, i);
        ans[i] = 0;
      }
    }
    solve(1, tot);
    for (int i = 1; i <= q; ++i) if (ans[i] != -1)  printf("%I64d\n", ans[i]);
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值