HH的项链 离线/在线 树状/线段树/主席树/莫队
题目
HH 有一串由各种漂亮的贝壳组成的项链。
HH 相信不同的贝壳会带来好运,所以每次散步完后,他都会随意取出一段贝壳,思考它们所表达的含义。
HH 不断地收集新的贝壳,因此他的项链变得越来越长。
有一天,他突然提出了一个问题:某一段贝壳中,包含了多少种不同的贝壳?
这个问题很难回答,因为项链实在是太长了。
于是,他只好求助睿智的你,来解决这个问题。
输入格式
第一行:一个整数 N,表示项链的长度。
第二行:N 个整数,表示依次表示项链中贝壳的编号(编号为 0 到 1000000 之间的整数)。
第三行:一个整数 M,表示 HH 询问的个数。
接下来 M 行:每行两个整数,L 和 R,表示询问的区间。
输出格式
M 行,每行一个整数,依次表示询问对应的答案。
数据范围
1≤N≤50000,
1≤M≤2×105,
1≤L≤R≤N
输入样例:
6
1 2 3 4 3 5
3
1 2
3 5
2 6
输出样例:
2
2
4
题解
离线算法
树状数组
这俩差不多, 树状更好写,
定住右区间 r 从1~n, 然后维护离 当前 r最近且没有出现过数字的位置
const int N = 5e4 + 5, M = 1e6 + 5;
int n, m, _, k;
int a[N], c[N], ls[M], ans[200005];
pair<PII, int> q[200005];
void add(int x, int k) {
for (; x <= n; x += -x & x) c[x] += k;
}
int ask(int x) {
int ans = 0;
for (; x; x -= -x & x) ans += c[x];
return ans;
}
bool cmp(const pair<PII, int>& a, const pair<PII, int>& b) {
return a.fi.se < b.fi.se;
}
int main() {
IOS; cin >> n;
rep(i, 1, n) cin >> a[i];
cin >> m;
rep(i, 1, m) cin >> q[i].fi.fi >> q[i].fi.se, q[i].se = i;
sort(q + 1, q + 1 + m, cmp);
for (int i = 1, j = 1; i <= m; ++i) {
for (; j <= q[i].fi.se; ++j) {
if (ls[a[j]]) add(ls[a[j]], -1);
add(j, 1); ls[a[j]] = j;
}
ans[q[i].se] = ask(q[i].fi.se) - ask(q[i].fi.fi - 1);
}
rep(i, 1, m) cout << ans[i] << '\n';
return 0;
}
线段树
和树状一样
线段树就不用我敲了吧? 这道题是单点修改, 直接push_down 不需要lazy标记, 算了敲敲吧~
struct BIT {
struct node {
int val, l, r;
} tr[N << 2];
void push_up(int rt) {
tr[rt].val = tr[rt << 1 | 1].val + tr[rt << 1].val;
}
void build(int rt, int l, int r) {
tr[rt] = { 0, l, r };
if (l == r) return;
int mid = l + r >> 1;
build(rt << 1, l, mid); build(rt << 1 | 1, mid + 1, r);
}
void change(int rt, int x, int k) {
if (tr[rt].l == x && tr[rt].r == x) { tr[rt].val += k; return; }
int mid = tr[rt].l + tr[rt].r >> 1;
if (x <= mid) change(rt << 1, x, k);
else change(rt << 1 | 1, x, k);
push_up(rt);
}
int ask(int rt, int l, int r) {
if (tr[rt].l >= l && tr[rt].r <= r) return tr[rt].val;
int ans = 0, mid = tr[rt].l + tr[rt].r >> 1;
if (mid >= l) ans = ask(rt << 1, l, r);
if (r > mid) ans += ask(rt << 1 | 1, l, r);
return ans;
}
} bit;
bool cmp(const pair<PII, int>& a, const pair<PII, int>& b) {
return a.fi.se < b.fi.se;
}
int main() {
IOS; cin >> n; bit.build(1, 1, n);
rep(i, 1, n) cin >> a[i];
cin >> m;
rep(i, 1, m) cin >> q[i].fi.fi >> q[i].fi.se, q[i].se = i;
sort(q + 1, q + 1 + m, cmp);
for (int i = 1, j = 1; i <= m; ++i) {
for (; j <= q[i].fi.se; ++j) {
if (ls[a[j]]) bit.change(1, ls[a[j]], -1);
bit.change(1, j, 1); ls[a[j]] = j;
}
ans[q[i].se] = bit.ask(1, q[i].fi.fi, q[i].fi.se);
}
rep(i, 1, m) cout << ans[i] << '\n';
return 0;
}
莫队
刚才是卡着 r 取修改区间, 离线
那在线怎呢办呢?
l~r 说白了就是 r - l + 1 - 重复数字的个数
怎么区分 重复数字的问题,
我们注意到 (a[i] == a[j], l < i < j < r) a[i]的下一个位置是 j, 而 j 的下一个位置 在 l~r内不存在, pre[i] 表示上一个
知道怎么算了吧, 直接记 l~r a[i] 下个位置不在l~r区间内的数的个数即可, add 和 sub 的时候判断是否要+-贡献就行了
const int N = 2e5 + 5, M = 1e6 + 5;
int n, m, _, k;
int h[M], pre[M], ne[M], ans[N];
pair<PII, int> q[N];
int add(int l, int r, int k) {
if (k > r && pre[k] < l) return 1;
if (k < l && ne[k] > r) return 1;
return 0;
}
int sub(int l, int r, int k) {
if (k == r && pre[k] < l) return 1;
if (k == l && ne[k] > r) return 1;
return 0;
}
int main() {
IOS; cin >> n;
rep(i, 1, 1e6) ne[i] = n + 1;
rep(i, 1, n) cin >> m, pre[i] = h[m], ne[h[m]] = i, h[m] = i;
cin >> m; int siz = sqrt(n);
rep(i, 1, m) cin >> q[i].fi.fi >> q[i].fi.se, q[i].se = i;
sort(q + 1, q + 1 + m, [&](pair<PII, int> a, pair<PII, int> b) {
return a.fi.fi / siz == b.fi.fi / siz ? a.fi.se < b.fi.se : a.fi.fi / siz < b.fi.fi / siz;
});
int l = 1, r = 0, val = 0;
rep(i, 1, m) {
for (; q[i].fi.se > r; val += add(l, r, r + 1), ++r);
for (; q[i].fi.se < r; val -= sub(l, r, r), --r);
for (; q[i].fi.fi < l; val += add(l, r, l - 1), --l);
for (; q[i].fi.fi > l; val -= sub(l, r, l), ++l);
ans[q[i].se] = val;
}
rep(i, 1, m) cout << ans[i] << '\n';
return 0;
}
在线算法
主席树
跟莫队思路基本一样, 去掉了pre[N]数组, 并在线静在线回答
const int N = 5e4 + 5, M = 1e6 + 5;
struct CHT {
struct node {
int val, lson, rson;
node (int Val = 0, int Ls = 0, int Rs = 0)
: val(Val), lson(Ls), rson(Rs){}
} tr[20 * N]; //log2(n)*n
int root[N], tot;
void update(int &x, int y, int l, int r, int k, int cnt) {
tr[x = ++tot] = tr[y]; tr[x].val += cnt;
if (l == r) return;
int mid = l + r >> 1;
if (k <= mid) update(tr[x].lson, tr[y].lson, l, mid, k, cnt);
else update(tr[x].rson, tr[y].rson, mid + 1, r, k, cnt);
}
int ask(int x, int y, int l, int r, int k) {
if (l >= k) return tr[x].val - tr[y].val;
int mid = l + r >> 1, ans = 0;
if (mid >= k) ans = ask(tr[x].lson, tr[y].lson, l, mid, k);
return ans + ask(tr[x].rson, tr[y].rson, mid + 1, r, k);
}
} bit;
int n, m, _, k;
int h[M], ne[M];
int main() {
IOS; cin >> n;
rep (i, 1, 1e6) ne[i] = n + 1;
rep(i, 1, n) cin >> m, ne[h[m]] = i, h[m] = i;
rep(i, 1, n) bit.update(bit.root[i], bit.root[i - 1], 1, n + 1, ne[i], 1);
cin >> m;
rep(i, 1, m) {
int l, r; cin >> l >> r;
cout << bit.ask(bit.root[r], bit.root[l - 1], 1, n + 1, r + 1) << '\n';
}
return 0;
}