【线段树】【P4062】 [Code+#1]Yazid 的新生舞会

Description

给定一个长度为 \(n\) 的序列,求有多少子区间满足区间众数严格大于区间长度的一半。如果区间有多个出现次数最多且不同的数则取较小的数为众数。

Limitation

对于全部的数据,\(1 \leq n \leq 500000\)

序列中数的值域为 \([0,n)\)

子任务:序列中的数值域为 \([0,7]\)

Solution

考虑如果区间有多个出现次数最多且不同的数,那么这个区间显然是不合法的。于是区间出现多个众数取最小的限制其实没有什么 * 用。

考虑枚举区间众数是 \(x\),将区间中等于 \(x\) 的数置为 \(1\),不等于的置为 \(-1\),考虑 \(x\) 是该区间的众数且出现次数严格大于区间长度一半的充要条件是区间的和大于 \(0\)

于是考虑问题被转化成了给定一个仅含有 \(-1\)\(1\) 的序列,求有多少子区间满足区间和大于 \(0\)

对于序列的子区间问题的一个经典套路是通过做前缀和转化成序列点对问题,这是因为子区间有 \(O(n^2)\) 个,而点只有 \(O(n)\) 个,子区间甚至遍历一遍复杂度都难以支持,但是单点只需要在遍历时维护对应信息即可。

考虑对序列做前缀和,那么 \([l,~r]\) 区间是合法的当且仅当 \(sum_r - sum_{l - 1} > 0\),也即 \(sum_r > sum_{l - 1}\)。于是只需要求前缀和序列的顺序对个数即可。

对于序列值域较小的子任务,可以直接枚举众数,然后每次用权值线段树/权值树状数组 \(O(n \log n)\) 去求顺序对个数。时间复杂度 \(O(\max_{i = 1}^{n}\{A_i\} \times n \log n)\)

考虑枚举这 \(O(n)\) 个众数一共产生 \(O(n)\)\(1,~-1\) 序列,共有 \(O(n^2)\) 个数,在这 \(O(n^2)\) 个数中只有 \(O(n)\)\(1\),剩下全是 \(-1\)。由于可以认为是这 \(O(n)\)\(1\)\(O(n)\) 个空白(即两个相邻序列)将这 \(O(n^2)\)\(-1\) 隔开,于是连续的 \(-1\) 序列有 \(O(n)\) 个。

考虑每段连续的 \(-1\) 序列,假设它们的前缀和值域是 \([l,~r]\),则它们对答案产生的贡献是

\[\sum_{i = l}^{r} \sum_{j = -n}^{i - 1} tree_i~=~(r - l + 1) \times \sum_{i = -n}^{l-1} tree_i + \sum_{i = l}^{r} tree_i \times (r - i)~=~(r - l + 1) \times \sum_{i = -n}^{l-1} tree_i + r\sum_{i = l}^{r} tree_i - \sum_{i = l}^{r} tree_i \times i\]

其中 \(tree_i\)\(i\) 出现的次数。

于是考虑只需要用权值线段树维护 \(tree_i\)\(tree_i \times i\) 即可。考虑将这连续一段 \(-1\) 插入可以区间 \(+1\)。对于不是 \(-1\) 的位置,显然可以单次 \(O(\log n)\) 计算。于是总复杂度 \(O(n \log n)\)

Code

#include 
#include 
#include 
#ifdef ONLINE_JUDGE
#define freopen(a, b, c)
#endif
#define printf(...)

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 
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 
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(x % 10 + '0');} while (x /= 10);
  while (top) putchar(OPT::buf[top--]);
  if (pt) putchar(aft);
}

const int maxn = 500005;

int n;
ll ans;
int MU[maxn];
std::vectornum[maxn];

struct Tree {
  int l, r;
  ll sum;
  Tree *ls, *rs;
  std::pair v;
  std::pair tag;
  
  Tree(const int _l, const int _r) : l(_l), r(_r), sum(((r - l + 1ll) * (r + l)) >> 1), ls(NULL), rs(NULL) {}

  inline void maketag(std::pair v) {
    if (v.second) {
      this->v.first = this->v.second = this->tag.first = 0;
      this->tag.second = true;
    }
    this->v.first += (r - l + 1ll) * v.first;
    this->v.second += v.first * sum;
    this->tag.first += v.first;
  }

  inline void pushdown() {
    this->ls->maketag(this->tag);
    this->rs->maketag(this->tag);
    this->tag.first = this->tag.second = 0;
  }

  inline void pushup() {
    this->v.first = this->ls->v.first + this->rs->v.first;
    this->v.second = this->ls->v.second + this->rs->v.second;
  }

  inline bool InRange(const int l, const int r) {
    return (this->l >= l) && (this->r <= r);
  }

  inline bool OutofRange(const int l, const int r) {
    return (this->l > r) || (this->r < l);
  }
};
Tree *rot;

void build(Tree *const u);
int calc(const int k, const int p);
void update(Tree *const u, const int l, const int r);
std::pair query(Tree *const u, const int l, const int r);

int main() {
  freopen("1.in", "r", stdin);
  qr(n); qr(ans); ans = 0;
  for (int i = 1; i <= n; ++i) {
    qr(MU[i]); num[MU[i]].push_back(i);
  }
  build(rot = new Tree(-n, n));
  for (int k = 0; k < n; ++k) {
    rot->maketag(std::make_pair(0, true));
    int l = 0, cnt = 0;
    for (auto u : num[k]) {
      if (u != l) {
        int r = u - 1;
        int ar = calc(cnt, l), al = calc(cnt, r);
        auto ret1 = query(rot, -n, al - 1), ret2 = query(rot, al, ar);
        ans += ret1.first * (ar - al + 1) + ret2.first * ar - ret2.second;
        update(rot, al, ar);
        printf("QWAQ%d %d %lld\n", k, u, ans);
      }
      int v = calc(++cnt, u);
      ans += query(rot, -n, v - 1).first;
      printf("QWAQ%d %d %lld\n", k, u, ans);
      update(rot, v, v);
      l = u + 1;
    }
    if (l <= n) {
      int r = n;
      int ar = calc(cnt, l), al = calc(cnt, r);
      auto ret1 = query(rot, -n, al - 1), ret2 = query(rot, al, ar);
      printf("EMM%lld %lld %lld %lld\n", ret1.first, ret1.second, ret2.first, ret2.second);
      ans += ret1.first * (ar - al + 1) + ret2.first * ar - ret2.second;
    }
    printf("QWQ%d %lld\n", k, ans);
  }
  qw(ans, '\n', true);
  return 0;
}

void update(Tree *const u, const int l, const int r) {
  if (u->InRange(l, r)) {
    u->maketag(std::make_pair(1, false));
  } else if (!u->OutofRange(l, r)) {
    u->pushdown();
    update(u->ls, l, r); update(u->rs, l, r);
    u->pushup();
  }
  printf("UPD%d %d %d %d %d %d\n", u->l, u->r, l, r, u->v.first, u->v.second);
}

std::pair query(Tree *const u, const int l, const int r) {
  printf("EMMQAQ%d %d %d %d %d\n", u->l, u->r, l, r, u->v.first);
  if (u->InRange(l, r)) {
    return u->v;
  } else if (u->OutofRange(l, r)) {
    return std::make_pair(0ll, 0ll);
  } else {
    u->pushdown();
    auto rl = query(u->ls, l, r), rr = query(u->rs, l, r);
    return std::make_pair(rl.first + rr.first, rl.second + rr.second);
    u->pushup();
  }
}

inline int calc(const int k, const int p) {
  return (k << 1) - p;
}

void build(Tree *const u) {
  if (u->l == u->r) return;
  int mid = (u->l + u->r) >> 1;
  build(u->ls = new Tree(u->l, mid)); build(u->rs = new Tree(mid + 1, u->r));
}

Summary

对于子区间问题,可以通过做前缀和转化成点对问题。

你可能感兴趣的:(【线段树】【P4062】 [Code+#1]Yazid 的新生舞会)