题目链接: Group
大致题意
有一个长度为 n n n的 1 1 1~ n n n排列, 保证序列中没有相同的元素. 有 m m m次询问, 每次询问 [ l , r ] [l, r] [l,r]中所有的元素可以分成多少个不相交的线段.
解题思路
解法一: 莫队
我们考虑用莫队去维护 [ l , r ] [l, r] [l,r]的信息. 设当前位置为 p o s pos pos, 元素值为 v a l val val. 如果 v a l + 1 val + 1 val+1和 v a l − 1 val - 1 val−1均出现过, 则答案 − 1 -1 −1. 否则, 若二者均未出现, 则答案 + 1 +1 +1. 其他情况答案不变.
因此我们可以通过一个桶来维护各个元素的出现情况.
解法二: 线段树
考虑到线段树不像莫队, 每次保证维护的信息只有查询区间 [ l , r ] [l, r] [l,r]的信息那样的优秀性质. 我们只能考虑每个位置对于答案的贡献.
设当前位置为 p o s pos pos, 元素值为 v a l val val. 如果此时 v a l − 1 val - 1 val−1出现过, 那么由于 v a l val val的出现, 因此 p o s v a l − 1 pos_{val - 1} posval−1处的贡献 − 1 -1 −1. 对于 v a l + 1 val + 1 val+1的情况同理.
但是每次询问的区间是任意的, 我们修改 p o s v a l ± 1 pos_{val \pm 1} posval±1处的贡献的前提是 v a l val val一定出现了, 但可能前一次的查询区间 [ l ′ , r ′ ] [l', r'] [l′,r′]包含了 p o s pos pos位置, 这次的 [ l , r ] [l, r] [l,r]就不包含 p o s pos pos位置了, 导致我们统计出了错误的答案.
我们考虑对于询问按照右端点进行排序. 这样当我们加入 p o s pos pos位置时, 如果出现过 v a l ± 1 val \pm 1 val±1, 我们再对 p o s v a l ± 1 pos_{val\pm1} posval±1进行修改. 此时如果询问区间包含了, p o s v a l ± 1 pos_{val\pm1} posval±1那么一定会包含位置 p o s pos pos.
因为我们已经按照询问区间的右端点排序了, 如果修改了 p o s v a l ± 1 pos_{val\pm1} posval±1, 表明 p o s v a l ± 1 pos_{val\pm1} posval±1在 p o s pos pos之前出现过, 因此上述说法成立.
如果 l > p o s v a l ± 1 l > pos_{val\pm1} l>posval±1呢? 如果我们的查询区间已经不包含 p o s v a l ± 1 pos_{val\pm1} posval±1, 那么修改这个位置的值也不会影响答案的正确性.
综上所述, 我们可以通过离线询问 + 线段树的方式解题.
AC代码
莫队
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 1E5 + 10, B = 320;
int w[N];
struct mo {
int l, r, id;
bool operator< (const mo& t) const {
if (l / B != t.l / B) return l < t.l;
return l / B & 1 ? r < t.r : r > t.r;
}
}; vector<mo> area;
int res[N];
bool vis[N]; int qsum;
void add(int c) {
vis[c] = 1;
if (vis[c - 1] and vis[c + 1]) qsum--;
else if (!vis[c - 1] and !vis[c + 1]) qsum++;
}
void sub(int c) {
vis[c] = 0;
if (vis[c - 1] and vis[c + 1]) qsum++;
else if (!vis[c - 1] and !vis[c + 1]) qsum--;
}
int main()
{
int T; cin >> T;
while (T--) {
int n, m; scanf("%d %d", &n, &m);
rep(i, n) scanf("%d", &w[i]);
rep(i, m) {
int l, r; scanf("%d %d", &l, &r);
area.push_back({ l, r, i });
}
sort(area.begin(), area.end());
int L = 1, R = 0;
for (auto& op : area) {
int l = op.l, r = op.r, id = op.id;
while (l < L) add(w[--L]);
while (r > R) add(w[++R]);
while (L < l) sub(w[L++]);
while (R > r) sub(w[R--]);
res[id] = qsum;
}
rep(i, m) printf("%d\n", res[i]);
while (L <= R) sub(w[L++]); // 清空
area.clear();
}
return 0;
}
线段树
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 1E5 + 10;
int w[N];
struct node {
int l, r;
int val;
}t[N << 2];
void pushup(int x) { t[x].val = t[x << 1].val + t[x << 1 | 1].val; }
void build(int l, int r, int x = 1) {
t[x] = { l, r, r - l + 1 };
if (l == r) return;
int mid = l + r >> 1;
build(l, mid, x << 1), build(mid + 1, r, x << 1 | 1);
}
void modify(int a, int c, int x = 1) {
if (t[x].l == t[x].r) {
t[x].val += c;
return;
}
int mid = t[x].l + t[x].r >> 1;
modify(a, c, x << 1 | (a > mid));
pushup(x);
}
int ask(int l, int r, int x = 1) {
if (l <= t[x].l and r >= t[x].r) return t[x].val;
int mid = t[x].l + t[x].r >> 1;
int res = 0;
if (l <= mid) res = ask(l, r, x << 1);
if (r > mid) res += ask(l, r, x << 1 | 1);
return res;
}
struct query {
int l, r, id;
bool operator< (const query& t) const { return r < t.r; }
}area[N];
int res[N];
int vis[N];
int main()
{
int T; cin >> T;
while (T--) {
int n, m; scanf("%d %d", &n, &m);
rep(i, n) scanf("%d", &w[i]), vis[i] = 0;
build(1, n);
rep(i, m) {
int l, r; scanf("%d %d", &l, &r);
area[i] = { l, r, i };
}
sort(area + 1, area + 1 + m);
int pos = 0;
rep(i, m) {
int l = area[i].l, r = area[i].r, id = area[i].id;
while (pos + 1 <= r) {
pos++;
int val = w[pos]; vis[val] = pos;
if (val - 1 >= 1 and vis[val - 1]) {
modify(vis[val - 1], -1);
}
if (val + 1 <= n and vis[val + 1]) {
modify(vis[val + 1], -1);
}
}
res[id] = ask(l, r);
}
rep(i, m) printf("%d\n", res[i]);
}
return 0;
}