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]≤1⋅106 )
解法
方法一(莫队):
- 这道题,区间 [ 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] [l−1,r],[l+1,r],[l,r−1],[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[l−1] 。
- 我们会发现一个问题,一个数可能既在区间 [ l , r ] [l,r] [l,r] 出现也在区间 [ 1 , l − 1 ] [1,l-1] [1,l−1] 出现,那么这个数字对这两个区间都贡献了答案,就不应该被减掉。
- 我们可以把询问的区间按照右端点 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[l−1] 就是这个区间的答案了。
- 也就是说,如果数字 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]);
}