luogu P1972 SDOI2009 HH的项链

P1972 SDOI2009 HH的项链 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

维护一个长度同为 \(n\) 的数组 \(b\)。一个指针 \(R\)\(1\) 扫到 \(n\) 并做如下操作:

  • 检查是否有 \(k <R\) 满足 \(a_k = a_R\)。若有,将 \(b_k\) 设为 \(0\)
  • \(b_R\) 设为 \(1\)
  • 回答所有右端点为 \(R\) 的询问 \([L, R]\),答案就是 \(b\)\([L, R]\) 上的区间和。

举例:\((1, 2, 1, 2, 1, 3)\)\(R\) 分别为 \(1, 2, 3, 4, 5, 6\) 时,\(b\) 数组的变化:

  • \(R = 1\)\((1, 0, 0, 0, 0, 0)\)
  • \(R = 2\)\((1, 1, 0, 0, 0, 0)\)
  • \(R=3\)\((0, 1, 1, 0, 0, 0)\),此时回答 \([1, 3]\)\(b_1 = 0\),相当于不再统计 \(a_1\)(因为已与 \(a_3\) 重复);
  • \(R = 4\)\((0, 0, 1, 1, 0, 0)\)
  • \(R = 5\)\((0, 0, 0, 1, 1, 0)\)
  • \(R = 6\)\((0,0, 0, 1, 1, 1)\)

\(b\) 使用树状数组加速。

/*
 * @Author: crab-in-the-northeast 
 * @Date: 2022-10-20 17:43:24 
 * @Last Modified by: crab-in-the-northeast
 * @Last Modified time: 2022-10-20 18:10:01
 */
#include <bits/stdc++.h>
inline int read() {
    int x = 0;
    bool flag = true;
    char ch = getchar();
    while (!isdigit(ch)) {
        if (ch == '-')
            flag = false;
        ch = getchar();
    }
    while (isdigit(ch)) {
        x = (x << 1) + (x << 3) + ch - '0';
        ch = getchar();
    }
    if(flag)
        return x;
    return ~(x - 1);
}
inline int lowbit(int x) {
    return x & (-x);
}

const int maxn = (int)1e6 + 5;
const int maxq = (int)1e6 + 5;
const int maxv = (int)1e6 + 5;

int a[maxn], lst[maxv], c[maxn];

int n;
inline void add(int x, int v) {
    for (; x <= n; x += lowbit(x))
        c[x] += v;
    return ;
}

inline int get(int x) {
    int sum = 0;
    for (; x; x -= lowbit(x))
        sum += c[x];
    return sum;
}

typedef std :: pair <int, int> pii;
std :: vector <pii> qst[maxn];
int ans[maxq];

int main() {
    n = read();
    for (int i = 1; i <= n; ++i)
        a[i] = read();
    
    std :: fill(lst + 1, lst + maxv - 3, n + 1);
    
    int q = read();

    for (int i = 1; i <= q; ++i) {
        int l = read(), r = read();
        qst[r].emplace_back(l, i);
    }

    for (int r = 1; r <= n; ++r) {
        int x = a[r];
        add(lst[x], -1);
        add(r, 1);
        lst[x] = r;
        
        for (pii q : qst[r]) {
            int l = q.first, id = q.second;
            ans[id] = get(r) - get(l - 1);
        }
    }

    for (int i = 1; i <= q; ++i)
        printf("%d\n", ans[i]);
    
    return 0;
}

思路历程:

考虑区间询问。其实也就是让区间内所有 \(x\) 只被统计一次。

那么考虑一个数组 \(b\)。初始时 \(b\) 都是 \(1\)。然后对于一次询问 \([l, r]\),如果在区间内有三个数 \(i, j, k\) 满足 \(a_i = a_j = a_k\),那么我们只考虑让其中一个 \(b\) 变为 \(1\),比如 \(b_i = 1\),然后其他的 \(b_j = b_k = 0\)。类似这样的操作,只需要查询 \(b\)\([l ,r]\) 内的区间和,就是答案。

那么具体来说让哪个值变为 \(1\) 呢?先考虑最左侧变为 \(1\)

发现很难高效地完成上面那个 \(b\) 的变化操作。。。每次询问都需要扫一遍 \([l, r]\),别太离谱了,,

那就考虑在整个数组上扫。然后考虑怎么应用到子区间。(丹钓战那题的思想)

发现不同的询问,某个元素的 \(b\) 值是不同的,比如 \((1, 2, 3, 3, 5)\)\([4, 5]\)\(b_4\) 需要是 \(1\),但 \([3, 5]\)\(b_4\) 就会变成 \(0\)

其实想一下也就是 \(l \le 3\) 的询问里,\(b_4\) 变为 \(0\) 呗。那么肯定考虑离线询问了。

是否每次能回答的询问都是类似于 \(l \le \cdots\) 的形式?对。

上面那个例子中,所有 \(l \le 3\) 的询问的 \(b_4\)\(0\)\(l > 3\) 的询问 \(b_4\)\(1\) 肯定是没有问题的。

考虑维护一个指针 \(L\),倒着扫,每次扫到 \(a_L\),检查一下之前有没有一个 \(a_k\) 等于这个 \(a_L\),如果有就把那个 \(b_k\) 翻成 \(0\)。 然后回答所有左端点为 \(L\) 的询问。

要不翻转一下,维护一个指针 \(R\)。正着扫。(有点别扭)

考虑维护一个指针 \(R\),每次扫到 \(a_R\),检查一下之前有没有一个 \(a_k = a_R\),若有,\(b_k\) 翻为 \(0\)。然后回答所有右端点为 \(R\) 的询问,询问如果是 \([L, R]\) 那就是求 \(b\)\([L, R]\) 区间和。

\(b\) 树状数组加速即可。

posted @ 2022-10-20 18:13  dbxxx  阅读(22)  评论(0编辑  收藏  举报