题目链接

LOJ3146
LuoguP5445

思路要点

我们称能够相互到达的子段为关键段。

维护关键段可以用线段树维护。

先考虑断开某一个关键段 $[l,r]$ 的某一条边 $k$,那么这个操作只对 $[l,k]$ 到 $[k+1,r]$ 内的答案有贡献。同理加入这条边也只对这两个关键段有贡献。

考虑将这个贡献差分,就得到了:

  • 若插入,在第 $i$ 个时刻,$[l,k]$ 到 $[k+1,r]$ 内的答案 $-i$
  • 若删除,在第 $i$ 个时刻,$[l,k]$ 到 $[k+1,r]$ 内的答案 $+i$

一个特判,如果每个询问的最后时刻 $t$,若这两个点是联通的,那么我们需要 $+t$。

接下来,我们将两个关键段抽象成二维坐标系内的 $x$ 轴和 $y$ 轴,也就是每次在 $(l,k+1,k,r)$ 内的矩形进行带权修改。

因此考虑离线分治计算答案,是一个二维数点问题。

时间复杂度 $\Theta (nlog^2n)$

代码

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

const int N = 3e5 + 5;

char s[N];
int n, q, etot;
int a[N], ans[N];
bool vis[N];

struct Node {
  pair<int, int> bd;
  bool tag;
} tr[N << 2];

struct Op {
  int t, x1, y1, x2, y2, id, v;
} op[N];

struct Event {
  int x, ys, ye, v;

  bool operator<(const Event& rhs) const {
    return x < rhs.x;
  }
} evt[N << 1];

int fw[N];

void pushdown(int x) {
  if (tr[x].tag) {
    tr[x << 1].bd = tr[x << 1 | 1].bd = tr[x].bd;
    tr[x << 1].tag = tr[x << 1 | 1].tag = 1;
    tr[x].tag = 0;
  }
}

void build(int x, int l, int r) {
  if (l == r) {
    tr[x].bd = make_pair(l, l);
    return;
  }
  int mid = (l + r) >> 1;
  build(x << 1, l, mid);
  build(x << 1 | 1, mid + 1, r);
}

void modify(int x, int l, int r, int ql, int qr, pair<int, int> val) {
  if (ql <= l && r <= qr) {
    tr[x].bd = val;
    tr[x].tag = 1;
    return;
  }
  pushdown(x);
  int mid = (l + r) >> 1;
  if (ql <= mid)
    modify(x << 1, l, mid, ql, qr, val);
  if (qr > mid)
    modify(x << 1 | 1, mid + 1, r, ql, qr, val);
}

pair<int, int> query(int x, int l, int r, int p) {
  if (l == r) 
    return tr[x].bd;
  pushdown(x);
  int mid = (l + r) >> 1;
  if (p <= mid) 
    return query(x << 1, l, mid, p);
  else 
    return query(x << 1 | 1, mid + 1, r, p);
}

void add(int p, int v) {
  for (int i = p; i <= n + 1; i += i & -i)
    fw[i] += v; 
}

int query(int p) {
  int ans = 0; 
  for (int i = p; i >= 1; i -= i & -i)
    ans += fw[i];
  return ans;
}

void addEvent(int x, int ys, int ye, int v) {
  evt[++etot] = (Event){x, ys, ye, v};
}

void solve(int l, int r) {
  if (l == r)
    return;
  int mid = (l + r) >> 1;
  solve(l, mid), solve(mid + 1, r);
  etot = 0;
  for (int i = l; i <= mid; i++)
    if (op[i].t == 1) {
      addEvent(op[i].x1, op[i].y1, op[i].y2, op[i].v);
      addEvent(op[i].x2 + 1, op[i].y1, op[i].y2, -op[i].v);
    }
  sort(evt + 1, evt + 1 + etot);
  sort(op + mid + 1, op + 1 + r, [](Op a, Op b) {
    return a.x1 < b.x1;
  });
  int pt = 1; 
  for (int i = mid + 1; i <= r; i++)
    if (op[i].t == 2) {
      while (pt <= etot && evt[pt].x <= op[i].x1) {
        add(evt[pt].ys, evt[pt].v);
        add(evt[pt].ye + 1, -evt[pt].v);
        pt++;
      }
      ans[op[i].id] += query(op[i].y1);
    }
  for (int i = 1; i < pt; i++) {
    add(evt[i].ys, -evt[i].v);
    add(evt[i].ye + 1, evt[i].v);
  }
}

int main() {
#ifndef ONLINE_JUDGE
  freopen("input.in", "r", stdin);
  freopen("output.out", "w", stdout);
#endif
  scanf("%d %d", &n, &q);
  scanf("%s", s);
  for (int i = 1; i <= n; i++)
    a[i] = s[i - 1] - '0';
  build(1, 1, n + 1);
  for (int i = 1; i <= n; i++) 
    if (a[i] == 1 && !vis[i]) {
      int it = i;
      vis[i] = 1; 
      while (a[it]) 
        it++, vis[it] = 1; 
      modify(1, 1, n + 1, i, it, make_pair(i, it));
    }
  memset(ans, 0, sizeof ans);
  memset(vis, 0, sizeof vis);
  char opt[10];
  for (int i = 1; i <= q; i++) {
    scanf("%s", opt);
    op[i].t = (opt[0] == 'q' ? 2 : 1);
    op[i].id = i; 
    if (op[i].t == 1) {
      int k; 
      scanf("%d", &k);
      if (a[k] == 1) {
        pair<int, int> p = query(1, 1, n + 1, k);
        op[i].x1 = p.first, op[i].x2 = k;
        op[i].y1 = k + 1, op[i].y2 = p.second;
        op[i].v = i; 
        modify(1, 1, n + 1, p.first, k, make_pair(p.first, k));
        modify(1, 1, n + 1, k + 1, p.second, make_pair(k + 1, p.second));
      } else {
        pair<int, int> p1 = query(1, 1, n + 1, k), p2 = query(1, 1, n + 1, k + 1);
        op[i].x1 = p1.first, op[i].x2 = k; 
        op[i].y1 = k + 1, op[i].y2 = p2.second;
        op[i].v = -i;
        modify(1, 1, n + 1, p1.first, p2.second, make_pair(p1.first, p2.second));
      }
      a[k] ^= 1;
    } else {
      scanf("%d %d", &op[i].x1, &op[i].y1);
      pair<int, int> p = query(1, 1, n + 1, op[i].x1);
      if (p.second >= op[i].y1 && p.first <= op[i].y1) 
        ans[i] += i; 
      vis[i] = 1; 
    }
  }
  memset(fw, 0, sizeof fw);
  solve(1, q);
  for (int i = 1; i <= q; i++)
    if (vis[i])
      printf("%d\n", ans[i]);
  return 0;
}

后言

  • 这道题目充分的运用了数形结合和差分的思想,这可以将每一个操作对整体答案的贡献独立处理。而且更加形象的理解。
最后修改:2020 年 04 月 03 日 09 : 07 AM
如果觉得我的文章对你有用,请随意赞赏