题目链接:k-d-sequence
题目大意
给一个长度为(n)的序列(a),要你找一个最长的区间,使得将区间内的数排序以后,最多加(k)个数使它构成公差为(d)的等差数列。长度相同取左端点最靠左的。
(nleq 200000,kleq 200000,dleq 10^9,|a_i|leq 10^9)
解析
一个小插曲:这题JZOJ上的数据很不靠谱,随意就水过了。CF上的数据强一点。
首先我们来分析一段区间([l,r])怎么样才满足条件:
- 没有相等的数(区间长度(>1))
- 所有数模(d)同余(这样才能保证公差为(d))
- (frac{max_{i=l}^{r} {a_i}-min_{i=l}^{r} {a_i}}{d}+1leq r-l+1+k)
前两个条件保证这个序列能够通过补数成为公差为(d)的等差数列,第三个条件保证要补的数不超过(k)个。
对于前两个条件,我们可以用一个数组maxr[i]
表示以(i)为左端点,满足这两个条件的最远右端点,这个值具有单调性,可以(O(n))做出来。
对于第三个条件,我们先把所有的(a_i)除以(d),然后变一下式子,得到:
此时式子右边仅跟(l)有关,我们考虑倒序枚举(l),若能用某个数据结构维护每个(r)对应的(b_r=max_{i=l}^{r}{a_i}-min_{i=l}^{r}{a_i} - r),就好做了。
考虑(l)的移动使(b_r)发生的变化,用个单调栈维护(a_l)作为最大(小)值向右最远延伸到的位置(f_l),那么现在(a_l)是老大了,原来的假老大都得消除影响,可以发现一个数作为最大值影响的是一个区间,我们要把一个区间减掉某个数再加上某个数,这不显然用线段树维护吗?
那么问题来了,如何查询?我们要一个最右的端点(r)使得(b_rleq k-l),并且还要满足(lleq rleq maxr[l])。
其实维护一个区间最小值就行了,在线段树上判断右边(b_r)的最小值是否(leq k-l),是就往右走。为了防止跳到(l)之前或者(maxr[l])之后去,我们先把这两段加上无穷大,做完再减掉就行了。
注意,单调栈的过程是跟着倒序枚举(l)的过程一起做的。
此外记得特判(d=0)的情况,不然要出锅。
Code
#include <cstdio>
#include <cstring>
#include <algorithm>
#define lson rt << 1
#define rson rt << 1 | 1
using namespace std;
typedef long long ll;
const int N = 200007;
const ll INF = 0x3f3f3f3f;
ll min(ll a, ll b) { return a < b ? a : b; }
int ans = 1, ansl = 1;
int n, k, len, maxr[N], c[N], mo[N];
ll d, a[N], b[N];
int buc[N];
int top[2], stk[N][2];
ll mi[N * 4], inc[N * 4];
void upd(int rt) { mi[rt] = min(mi[lson], mi[rson]); }
void build(int rt, int l, int r)
{
if (l == r) { mi[rt] = -l; return; }
int mid = l + r >> 1;
build(lson, l, mid), build(rson, mid + 1, r), upd(rt);
}
void down(int rt, int l, int r)
{
if (!inc[rt]) return;
if (l != r) inc[lson] += inc[rt], inc[rson] += inc[rt];
mi[rt] += inc[rt], inc[rt] = 0;
}
void modify(int rt, int l, int r, int ql, int qr, ll v)
{
down(rt, l, r);
if (ql <= l && r <= qr) { inc[rt] += v; return; }
int mid = l + r >> 1;
if (ql <= mid) modify(lson, l, mid, ql, qr, v);
if (mid + 1 <= qr) modify(rson, mid + 1, r, ql, qr, v);
down(lson, l, mid), down(rson, mid + 1, r), upd(rt);
}
int getit(int rt, int l, int r, ll v)
{
down(rt, l, r);
if (l == r) return mi[rt] <= v ? l : -1;
int mid = l + r >> 1;
down(lson, l, mid), down(rson, mid + 1, r);
if (mi[rson] <= v) return getit(rson, mid + 1, r, v);
else return getit(lson, l, mid, v);
}
void doit()
{
for (int i = 1; i <= n; i++)
{
int j = i;
while (j < n && a[j + 1] == a[i]) j++;
if (j - i + 1 > ans) ans = j - i + 1, ansl = i;
else if (j - i + 1 == ans && i < ansl) ansl = i;
i = j;
}
printf("%d %d
", ansl, ansl + ans - 1);
}
int main()
{
scanf("%d%d%I64d", &n, &k, &d); //cf的%I64d
for (int i = 1; i <= n; i++) scanf("%I64d", &a[i]), a[i] += 1e9;
if (d == 0) { doit(); return 0; }
memcpy(b, a, sizeof(a));
sort(b + 1, b + n + 1);
len = unique(b + 1, b + n + 1) - b - 1;
for (int i = 1; i <= n; i++) c[i] = lower_bound(b + 1, b + n + 1, a[i]) - b, mo[i] = a[i] % d;
buc[c[1]]++;
for (int i = 1, j = 1; i <= n; i++)
{
while (j + 1 <= n && mo[j + 1] == mo[i] && !buc[c[j + 1]]) j++, buc[c[j]]++;
maxr[i] = j, buc[c[i]]--;
}
build(1, 1, n);
stk[0][0] = stk[0][1] = n + 1;
for (int i = n; i; i--)
{
a[i] /= d;
if (!top[0]) modify(1, 1, n, i, n, -a[i]);
if (!top[1]) modify(1, 1, n, i, n, a[i]);
for (; top[0] && a[i] < a[stk[top[0]][0]]; top[0]--) modify(1, 1, n, stk[top[0]][0], stk[top[0] - 1][0] - 1, a[stk[top[0]][0]] - a[i]);
for (; top[1] && a[i] > a[stk[top[1]][1]]; top[1]--) modify(1, 1, n, stk[top[1]][1], stk[top[1] - 1][1] - 1, a[i] - a[stk[top[1]][1]]);
stk[++top[0]][0] = stk[++top[1]][1] = i;
if (maxr[i] < n) modify(1, 1, n, maxr[i] + 1, n, INF);
if (i > 1) modify(1, 1, n, 1, i - 1, INF);
int r = getit(1, 1, n, k - i);
if (r != -1 && r - i + 1 >= ans) ans = r - i + 1, ansl = i;
if (maxr[i] < n) modify(1, 1, n, maxr[i] + 1, n, -INF);
if (i > 1) modify(1, 1, n, 1, i - 1, -INF);
}
printf("%d %d
", ansl, ans + ansl - 1);
return 0;
}