P1972 [SDOI2009]HH的项链 莫队&&树状数组

HH的项链 洛谷P1972

题意

一个长度为 n n n 的序列,共 m m m 组询问,询问区间 [ l , r ] [l,r] [l,r] 内不同数字的个数。

n n n , m m m , a [ i ] ≤ 1 ⋅ 1 0 6 a[i] \le 1\cdot 10^6 a[i]1106

解法

方法一(莫队):

  • 这道题,区间 [ l , r ] [l,r] [l,r] 的答案可以 O ( 1 ) O(1) O(1) 向区间 [ l − 1 , r ] , [ l + 1 , r ] , [ l , r − 1 ] , [ l , r + 1 ] [l-1,r],[l+1,r],[l,r-1],[l,r+1] [l1,r],[l+1,r],[l,r1],[l,r+1] 转移,所以可以用莫队算法。
  • 只需要维护一个 c n t cnt cnt 数组,记录每个数字出现的次数,然后增加或者删除一个数字可以知道区间内不同数字的个数的变化。从而 O ( 1 ) O(1) O(1) 转移答案。
  • 复杂度为 O ( n n ) O(n \sqrt n) O(nn ) ,只能过 5 e 5 5e5 5e5 的数据, 1 e 6 1e6 1e6 的数据会TLE。
#pragma region
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <vector>
using namespace std;
typedef long long ll;
#define rep(i, a, n) for (int i = a; i <= n; ++i)
#define per(i, a, n) for (int i = n; i >= a; --i)
namespace fastIO {
#define BUF_SIZE 100000
#define OUT_SIZE 100000
//fread->R
bool IOerror = 0;
//inline char nc(){char ch=getchar();if(ch==-1)IOerror=1;return ch;}
inline char nc() {
    static char buf[BUF_SIZE], *p1 = buf + BUF_SIZE, *pend = buf + BUF_SIZE;
    if (p1 == pend) {
        p1 = buf;
        pend = buf + fread(buf, 1, BUF_SIZE, stdin);
        if (pend == p1) {
            IOerror = 1;
            return -1;
        }
    }
    return *p1++;
}
inline bool blank(char ch) { return ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t'; }
template <class T>
inline bool R(T &x) {
    bool sign = 0;
    char ch = nc();
    x = 0;
    for (; blank(ch); ch = nc())
        ;
    if (IOerror)
        return false;
    if (ch == '-')
        sign = 1, ch = nc();
    for (; ch >= '0' && ch <= '9'; ch = nc())
        x = x * 10 + ch - '0';
    if (sign)
        x = -x;
    return true;
}
inline bool R(double &x) {
    bool sign = 0;
    char ch = nc();
    x = 0;
    for (; blank(ch); ch = nc())
        ;
    if (IOerror)
        return false;
    if (ch == '-')
        sign = 1, ch = nc();
    for (; ch >= '0' && ch <= '9'; ch = nc())
        x = x * 10 + ch - '0';
    if (ch == '.') {
        double tmp = 1;
        ch = nc();
        for (; ch >= '0' && ch <= '9'; ch = nc())
            tmp /= 10.0, x += tmp * (ch - '0');
    }
    if (sign)
        x = -x;
    return true;
}
inline bool R(char *s) {
    char ch = nc();
    for (; blank(ch); ch = nc())
        ;
    if (IOerror)
        return false;
    for (; !blank(ch) && !IOerror; ch = nc())
        *s++ = ch;
    *s = 0;
    return true;
}
inline bool R(char &c) {
    c = nc();
    if (IOerror) {
        c = -1;
        return false;
    }
    return true;
}
template <class T, class... U>
bool R(T &h, U &... tmp) { return R(h) && R(tmp...); }
#undef OUT_SIZE
#undef BUF_SIZE
};  // namespace fastIO
using namespace fastIO;
template <class T>
void _W(const T &x) { cout << x; }
void _W(const int &x) { printf("%d", x); }
void _W(const int64_t &x) { printf("%lld", x); }
void _W(const double &x) { printf("%.16f", x); }
void _W(const char &x) { putchar(x); }
void _W(const char *x) { printf("%s", x); }
template <class T, class U>
void _W(const pair<T, U> &x) { _W(x.F), putchar(' '), _W(x.S); }
template <class T>
void _W(const vector<T> &x) {
    for (auto i = x.begin(); i != x.end(); _W(*i++))
        if (i != x.cbegin()) putchar(' ');
}
void W() {}
template <class T, class... U>
void W(const T &head, const U &... tail) { _W(head), putchar(sizeof...(tail) ? ' ' : '\n'), W(tail...); }
#pragma endregion
//莫队维护区间颜色 洛谷P1972
const int maxn = 1e6 + 5;
int n, m, B, a[maxn];
int pos[maxn], ANS[maxn];
int ans = 0, cnt[maxn];
struct Req {
    int l, r, id;
    bool operator<(const Req &A) const {
        if (pos[l] != pos[A.l]) return pos[l] < pos[A.l];
        return r < A.r;
    }
} q[maxn];
void update(int id, int f) {
    int x = a[id];
    cnt[x] += f;
    if (f == 1 && cnt[x] == 1) ++ans;
    if (f == -1 && cnt[x] == 0) --ans;
}
int main() {
    R(n);
    B = sqrt(n);
    rep(i, 1, n) {
        R(a[i]);
        pos[i] = i / B;  //卡常
    }
    R(m);
    rep(i, 1, m) {
        R(q[i].l, q[i].r);
        q[i].id = i;
    }
    sort(q + 1, q + 1 + m);
    int l = 1, r = 0;
    rep(i, 1, m) {
        while (l < q[i].l) update(l++, -1);
        while (l > q[i].l) update(--l, 1);
        while (r < q[i].r) update(++r, 1);
        while (r > q[i].r) update(r--, -1);
        ANS[q[i].id] = ans;
    }
    rep(i, 1, m) W(ANS[i]);
}

方法二(树状数组):

  • 对于若干个询问的区间 [ l , r ] [l,r] [l,r] ,如果他们的 r r r 都相等的话,那么数组中出现的同一个数字,一定是只关心出现在最右边的那一个的。
  • 考虑前缀和,即区间 [ l , r ] [l,r] [l,r] 的答案为 s u m [ r ] − s u m [ l − 1 ] sum[r]-sum[l-1] sum[r]sum[l1]
  • 我们会发现一个问题,一个数可能既在区间 [ l , r ] [l,r] [l,r] 出现也在区间 [ 1 , l − 1 ] [1,l-1] [1,l1] 出现,那么这个数字对这两个区间都贡献了答案,就不应该被减掉。
  • 我们可以把询问的区间按照右端点 r r r 进行排序,维护一个树状数组,如果一个数字在 [ 1 , r ] [1,r] [1,r] 内出现了两次,那么我们只需要关心后一次的出现即可,这样 s u m [ r ] − s u m [ l − 1 ] sum[r]-sum[l-1] sum[r]sum[l1] 就是这个区间的答案了。
  • 也就是说,如果数字 a [ r ] a[r] a[r] 出现了,就删除上一次的出现,即 a d d ( p [ a [ r ] ] , − 1 ) add(p[a[r]],-1) add(p[a[r]],1) 。然后记录这一次的出现,即 a d d ( r , 1 ) add(r,1) add(r,1) ,并更新 p [ a [ r ] ] p[a[r]] p[a[r]]
#pragma region
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <vector>
using namespace std;
typedef long long ll;
#define rep(i, a, n) for (int i = a; i <= n; ++i)
#define per(i, a, n) for (int i = n; i >= a; --i)
namespace fastIO {
#define BUF_SIZE 100000
#define OUT_SIZE 100000
//fread->R
bool IOerror = 0;
//inline char nc(){char ch=getchar();if(ch==-1)IOerror=1;return ch;}
inline char nc() {
    static char buf[BUF_SIZE], *p1 = buf + BUF_SIZE, *pend = buf + BUF_SIZE;
    if (p1 == pend) {
        p1 = buf;
        pend = buf + fread(buf, 1, BUF_SIZE, stdin);
        if (pend == p1) {
            IOerror = 1;
            return -1;
        }
    }
    return *p1++;
}
inline bool blank(char ch) { return ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t'; }
template <class T>
inline bool R(T &x) {
    bool sign = 0;
    char ch = nc();
    x = 0;
    for (; blank(ch); ch = nc())
        ;
    if (IOerror)
        return false;
    if (ch == '-')
        sign = 1, ch = nc();
    for (; ch >= '0' && ch <= '9'; ch = nc())
        x = x * 10 + ch - '0';
    if (sign)
        x = -x;
    return true;
}
inline bool R(double &x) {
    bool sign = 0;
    char ch = nc();
    x = 0;
    for (; blank(ch); ch = nc())
        ;
    if (IOerror)
        return false;
    if (ch == '-')
        sign = 1, ch = nc();
    for (; ch >= '0' && ch <= '9'; ch = nc())
        x = x * 10 + ch - '0';
    if (ch == '.') {
        double tmp = 1;
        ch = nc();
        for (; ch >= '0' && ch <= '9'; ch = nc())
            tmp /= 10.0, x += tmp * (ch - '0');
    }
    if (sign)
        x = -x;
    return true;
}
inline bool R(char *s) {
    char ch = nc();
    for (; blank(ch); ch = nc())
        ;
    if (IOerror)
        return false;
    for (; !blank(ch) && !IOerror; ch = nc())
        *s++ = ch;
    *s = 0;
    return true;
}
inline bool R(char &c) {
    c = nc();
    if (IOerror) {
        c = -1;
        return false;
    }
    return true;
}
template <class T, class... U>
bool R(T &h, U &... tmp) { return R(h) && R(tmp...); }
#undef OUT_SIZE
#undef BUF_SIZE
};  // namespace fastIO
using namespace fastIO;
template <class T>
void _W(const T &x) { cout << x; }
void _W(const int &x) { printf("%d", x); }
void _W(const int64_t &x) { printf("%lld", x); }
void _W(const double &x) { printf("%.16f", x); }
void _W(const char &x) { putchar(x); }
void _W(const char *x) { printf("%s", x); }
template <class T, class U>
void _W(const pair<T, U> &x) { _W(x.F), putchar(' '), _W(x.S); }
template <class T>
void _W(const vector<T> &x) {
    for (auto i = x.begin(); i != x.end(); _W(*i++))
        if (i != x.cbegin()) putchar(' ');
}
void W() {}
template <class T, class... U>
void W(const T &head, const U &... tail) { _W(head), putchar(sizeof...(tail) ? ' ' : '\n'), W(tail...); }
#pragma endregion
//离线树状数组维护区间颜色 洛谷P1972
const int maxn = 1e6 + 5;
int n, Q, a[maxn], c[maxn];
int ANS[maxn];
struct node {
    int l, r, id;
    bool operator<(const node &A) const { return r < A.r; }
} q[maxn];
void add(int pos, int x) {
    while (pos <= n) c[pos] += x, pos += pos & -pos;
}
int getsum(int pos) {
    int ans = 0;
    while (pos) ans += c[pos], pos -= pos & -pos;
    return ans;
}
int p[maxn];
int main() {
    R(n);
    rep(i, 1, n) R(a[i]);
    R(Q);
    rep(i, 1, Q) R(q[i].l, q[i].r), q[i].id = i;
    sort(q + 1, q + 1 + Q);
    int r = 0;
    rep(i, 1, Q) {
        while (r < q[i].r) {
            ++r;
            if (p[a[r]]) add(p[a[r]], -1);
            add(r, 1);
            p[a[r]] = r;
        }
        ANS[q[i].id] = getsum(q[i].r) - getsum(q[i].l - 1);
    }
    rep(i, 1, Q) W(ANS[i]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值