Reading

CodeForces 351D Jeff and Removing Periods


Fanyi Pu

原题链接

有一个长度为 n(n≤105) 的序列 {a}(ai≤105),你可以对它进行操作,操作如下:首先选择三个数 v,t,k,满足 av=av+t=av+2t=⋯=av+kt,然后将其删除,得到一个新的序列。每次操作结束后你都能将新数列重排。

现有 Q(Q≤105) 个询问,每次询问 [l,r] 表示问要把 [l,r] 删除所需的最小步数。

(以上翻译改写自洛谷)

题解

感觉像是 HH 的项链 的升级版。

不难发现答案就是 [l,r] 不同数的个数 + 第一次在 [l,r] 中是否有一种数被删掉。

记两个数组 fa[i] 与 ffa[i],fa[i] 表示上一次第 i 个数出现在了那里,ffa[i] 表示如果询问的 l≤ffa[i],那么在 [l,r] 区间内与第 i 个数相同的不可能被一次性删除。

大致就是这样一幅图:

fa 与 ffa 的指向关系序列为 2、1、3、3、3、3、1、3、1、1。每列上方是数值 a[i],下方是从 1 开始的位置 i。上方实线由 i 指向 fa[i]:4 指向 3,5 指向 4,6 指向 5,7 指向 2,8 指向 6,9 指向 7,10 指向 9。下方红色虚线由 i 指向 ffa[i]:8 指向 5,9 指向 2,10 指向 7。指向 0 的边不绘制。fa[i]上一次出现ffa[i]非等距边界a[i]i211233343536173819110
fa 与 ffa 的指向关系(左右滑动查看完整图示)

上方实线表示 fa[i],下方红色虚线表示 ffa[i](如果 fa[i] 或 ffa[i] 连到了 0 就没画)。

不难得出如果 i−fa[i]=fa[i]−fa[fa[i]],那么 ffa[i]=ffa[fa[i]],否则 ffa[i]=fa[fa[i]]。

cpp
for(int i = 1, aa; i <= n; ++i)
{
    scanf("%d", &aa);
    fa[i] = lst[aa];
    lst[aa] = i;
    ffa[i] = (fa[i] - fa[fa[i]] == i - fa[i]) ? ffa[fa[i]] : fa[fa[i]];
}

我们考虑离线,让询问按 r 为关键字排序。

考虑查询 [l,r],如果是这样一段序列:1,2,2,2,3,我们发现 2 出现了 3 次,我们钦定最后一个 2 对答案有贡献。这样我们对所有相同的数,都钦定最后一个对答案有贡献,其余对答案即可。这样没加入一个数,就在线段树/树状数组对 i 位置加 1,在 fa[i] 位置减 1 即可(把前一个数的贡献删去)。

对于询问第一次能否完全删除一个数,我们只需查询有多少个数在 [l,r] 上不是均匀分布即可。同理,我们钦定最后一个对答案有贡献,没加入一个 i 时,在 ffa[fa[i]] 上 −1,在 ffa[i] 上 +1 即可。

代码

cpp
#include <cstdio>
#include <iostream>
#include <algorithm>

using namespace std;

const int maxn = 100005;

int n;

struct Tree
{
    int no[maxn << 2];
    int K;

    inline void build_tree()
    {
        for(K = 1; K <= n; K <<= 1);
    }

    inline void add(int k, int x)
    {
        if(!k)
            return;
        for(k += K; k; k >>= 1)
            no[k] += x;
    }

    inline int query(int l, int r)
    {
        int ans = 0;
        for(l += K - 1, r += K + 1; l ^ r ^ 1; l >>= 1, r >>= 1)
        {
            if(~l & 1)
                ans += no[l ^ 1];
            if(r & 1)
                ans += no[r ^ 1];
        }
        return ans;
    }
} tr1, tr2;

int fa[maxn], ffa[maxn], lst[maxn];

struct ask
{
    int l, r, id;

    friend bool operator < (ask a, ask b)
    {
        return a.r < b.r;
    }
} q[maxn];

int ans[maxn];

int main()
{
    scanf("%d", &n);
    tr1.build_tree(), tr2.build_tree();
    for(int i = 1, aa; i <= n; ++i)
    {
        scanf("%d", &aa);
        fa[i] = lst[aa];
        lst[aa] = i;
        ffa[i] = (fa[i] - fa[fa[i]] == i - fa[i]) ? ffa[fa[i]] : fa[fa[i]];
    }
    int Q;
    scanf("%d", &Q);
    for(int i = 1; i <= Q; ++i)
    {
        scanf("%d%d", &q[i].l, &q[i].r);
        q[i].id = i;
    }
    sort(q + 1, q + Q + 1);
    for(int i = 1, now = 0; i <= Q; ++i)
    {
        register int l = q[i].l, r = q[i].r;
        while(now < r)
        {
            now++;
            tr1.add(fa[now], -1);
            tr1.add(now, 1);
            tr2.add(ffa[fa[now]], -1);
            tr2.add(ffa[now], 1);
        }
        int tt1 = tr1.query(l, r), tt2 = tr2.query(l, r);
        ans[q[i].id] = tt1 + (tt1 == tt2);
    }
    for(int i = 1; i <= Q; ++i)
        printf("%d\n", ans[i]);
    return 0;
}

Cite this post

@misc{pu2018oiicpccodeforcescf351d,
  author = {Pu, Fanyi},
  title  = {CodeForces 351D Jeff and Removing Periods},
  year   = {2018},
  month  = {12},
  url    = {https://pufanyi.com/blog/oi-icpc/codeforces/cf351d}
}