CF407E k-d-sequence

思路

要素察觉:必须要是一个公差为 \(d\) 的等差数列。

特判

首先要特判掉 \(d=0\) 的情况,这样的情况下就是要寻找最长的一段数字相同的区间,找到之后输出左右端点即可(可以 \(O(n)\) 扫一遍)。

其他情况

再来看别的情况,对于一个区间 \([l,r]\),如果要满足是一个公差为 \(d\) 的等差序列,那么:

  • 这个等差数列里的所有数 \(\bmod d\) 的结果应该一样.
  • 区间内没有重复的数.

现在考虑如何实现上述条件:

  • 首先将序列分成若干个 \(x \bmod d\) 都一样的子区间。

    在从左往右扫的过程中,如果遇到了与前面 \(x\bmod d\) 的值不同的数,就将左边 \(x\bmod d\) 值相同的数作为一个独立的区间来处理,这样就可以把序列分成若干个 \(x \bmod d\) 都一样的区间。

  • 对于一个满足 \(x\bmod d=c\) 的数列,把所有的 \(x\) 变成 \(\dfrac{x-c}{d}\) (因为整形的性质,可以直接除)。

    这一步称之为归一化,实现了将一个区间的的公差化为 \(1\)

    问题就转化成了加入 \(k\) 个数,使区间排序后公差为 \(1\)

  • 对于一个区间 \([L,R]\),算出最少加几个数。

    需要满足的条件显然就是不能有重复,那么最少加的数的个数就是 \(\max(L,R)-\min(L,R)+1-(R-L+1)\)

  • 从小到大枚举 \(R\)

    那么问题就相当于求最小的 \(L\),使得

    • \([L,R]\) 无重复。

      从小到大枚举 \(R\),对于新的 \(a[R]\) 很容易知道它前面一个和他相等的数 \(a[T]\) (可以用\(map\)实现),那么 \(L\) 至少要大于 \(T\)

    • 加的数不能多于 \(k\) 个。

      也就是 \(\max(L,R)-\min(L,R)+1-(R-L+1)\le k\),转化一下即 \(\max(L,R)-\min(L,R)+L\le k + R\)

      用线段树维护 \(w[L]=\max(L,R)-\min(L,R)+L\)

      假如 \(L\) 的下界是 \(T\),那么我们要在 \([T+1,R]\) 中找最左的位置使得 \(w\le k + R\)

如果能完成上述过程,这道题就做完了,那么现在的问题是,如何维护 \(w\)

维护 \(w\)的方法

用单调栈。维护一个单调递减的栈和一个单调递增的栈,两个栈的维护方法是同理的,此处以单调递减的栈为例进行讲解。

因为单调栈递减的性质,所以当一个大于栈顶的元素加入时,会不断地弹出栈顶,直到栈顶元素大于此元素为止,再将此元素入栈,由此,单调栈可以将 \(\max(L,R)\) 分成递减的若干段,考虑是如何实现的:

  1. 如图所示,假设有一个单调递减的单调栈,其中 \(S1> S2> S3\)\(S3\) 为栈顶元素。

    CF407E k-d-sequence_第1张图片

  2. 由于单调栈的性质,\(S1\) 和栈中上一个元素之间可能是有别的元素的,所以 \(S1,S2,S3\) 其实代表了三个区间的最大值。

    CF407E k-d-sequence_第2张图片

  3. 此时,单调栈中来了一个新的元素 \(a\),显然 \(a>S1>S2>S3\),为了维护单调栈的性质,所以我们要将 \(S1,S2,S3\) 弹栈。

    CF407E k-d-sequence_第3张图片

  4. 在弹栈时,因为此时这三个元素的值不能代表上述三个段的最大值了,所以我们需要将三个段的贡献从线段树中减去。

    CF407E k-d-sequence_第4张图片

  5. 同时,新的元素 \(a\) 就加进来了,这个时候就可以发现原来三个段的代表元素被弹出之后,其实就被新元素所接管了,整个区间的最大值变成 \(a\) 元素的值,所以再对一整个区间进行区间加操作。

    CF407E k-d-sequence_第5张图片

这样单调栈就实现了将 \(\max(L,R)\) 分成了递减的若干段,由此就可以不断进行段树的区间加、区间减。

\(\min\) 的维护与 \(\max\) 同理。

总时间复杂度 \(O(n\log n)\)

代码

/*
Author:loceaner
线段树,单调栈 
*/
#include 
#include 
#include 
#include 
#include 
#include 
#define lson rt << 1
#define rson rt << 1 | 1
using namespace std;

const int A = 2e5 + 11;
const int B = 1e6 + 11;
const int mod = 1e9;
const int inf = 0x3f3f3f3f;

inline int read() {
  char c = getchar();
  int x = 0, f = 1;
  for ( ; !isdigit(c); c = getchar()) if (c == '-') f = -1;
  for ( ; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48);
  return x * f;
}

bool flag;
map  last;
int sl[A], topl, sr[A], topr;
struct tr { int l, r, lazy, w; } t[A << 2];
int n, k, d, ansl = 1, ansr = 1, a[A], ans;

inline void pushup(int rt) {
  t[rt].w = min(t[lson].w, t[rson].w);
}

inline void calc(int rt, int x) {
  t[rt].lazy += x, t[rt].w += x;
}

inline void pushdown(int rt) {
  if (t[rt].lazy && t[rt].l < t[rt].r) {
    calc(lson, t[rt].lazy), calc(rson, t[rt].lazy);
    t[rt].lazy = 0;
  }
}

void build(int rt, int l, int r) {
  t[rt].l = l, t[rt].r = r, t[rt].lazy = 0, t[rt].w = 0;
  if (l == r) { t[rt].w = l; return; }
  int mid = (l + r) >> 1;
  build(lson, l, mid), build(rson, mid + 1, r);
  pushup(rt);
}

void insert(int rt, int l, int r, int k) {
  if (l <= t[rt].l && t[rt].r <= r) return calc(rt, k);
  pushdown(rt);
  int mid = (t[rt].l + t[rt].r) >> 1;
  if (l <= mid) insert(lson, l, r, k);
  if (r > mid) insert(rson, l, r, k);
  pushup(rt);
}

int work(int rt, int k) {
  if (t[rt].l == t[rt].r) return t[rt].l;
  pushdown(rt);
  if (t[lson].w <= k) return work(lson, k);
  else return work(rson, k);
}

void query(int rt, int l, int r, int k) {
  if (l <= t[rt].l && t[rt].r <= r) {
    if (t[rt].w <= k) flag = 1, ans = work(rt, k);
    return;
  }
  pushdown(rt);
  int mid = (t[rt].l + t[rt].r) >> 1;
  if (l <= mid && !flag) query(lson, l, r, k);
  if (r > mid && !flag) query(rson, l, r, k);
}

void del(int rt, int x) {
  if (x < t[rt].l || x > t[rt].r) return;
  pushdown(rt);
  if (t[rt].l == x && t[rt].r == x) { t[rt].w = 0; return; }
  del(lson, x), del(rson, x);
  pushup(rt);
}

int main() {
  n = read(), k = read(), d = read();
  for (int i = 1; i <= n; i++) a[i] = read() + inf;
  if (d == 0) {
    for (int i = 1; i <= n; i++) {
      int l = i;
      while (a[i] == a[i + 1] && i < n) i++;
      if (ansr - ansl < i - l) ansl = l, ansr = i;
    }
    cout << ansl << ' ' << ansr << '\n';
    return 0;
  }
  build(1, 1, n);
  //枚举右端点 
  for (int i = 1, L = 1; i <= n; i++) {
    int tmp = L;
    if (a[i] % d == a[i - 1] % d) L = max(L, last[a[i]] + 1);
    else L = i;
    last[a[i]] = i;
    while (tmp < L) del(1, tmp++);
    //递增栈 
    while (topl && sl[topl] >= L && a[sl[topl]] >= a[i]) {
      insert(1, max(L, sl[topl - 1] + 1), sl[topl], a[sl[topl]] / d);
      topl--;
    }
    insert(1, max(L, sl[topl] + 1), i, -a[i] / d);
    sl[++topl] = i;
    //递减栈
    while (topr && sr[topr] >= L && a[sr[topr]] <= a[i]) {
      insert(1, max(L, sr[topr - 1] +1), sr[topr], -a[sr[topr]] / d);
      topr--;
    }
    insert(1, max(L, sr[topr] + 1), i, a[i] / d);
    sr[++topr] = i;
    flag = 0, ans = 0;
    query(1, L, i, k + i); //思路中的式子 
    if (ansr - ansl < i - ans) ansl = ans, ansr = i;
  }
  cout << ansl << " " << ansr << '\n';
  return 0;
}

你可能感兴趣的:(CF407E k-d-sequence)