我看没人写这题题解,那本菜鸡来写一下
题意:
给长度为n的序列,m次询问,每次询问区间 [ l , r ] [l,r] [l,r]中有多少数符合该数出现的个数等于它本身
思路:
这题的题意非常符合莫队算法
- 1:它是可以进行离线操作的
- 2:他是求区间解的
- 3:它的常数不是很大
- 4:莫队的优势:非常的好想,我看网上有树状数组,前缀和的题解,这可不是很好想的,可能看了题解后,都比较懵逼,莫队的话就十分好想,就看贡献什么时候该加什么时候该减即可
莫队算法的时间复杂度是 O ( n s q r t ( n ) ) O(nsqrt(n)) O(nsqrt(n)),所以如果常数很大的话,会tle,但这题的数据量才 1 e 5 1e5 1e5开跟后也不是很大,所以用莫队解决很轻松。
莫队算法的主要思想其实就是区间移动,举个栗子:
假如a数组有
5
5
5个数:
[
1
,
2
,
3
,
4
,
5
]
[1, 2, 3, 4, 5]
[1,2,3,4,5],我们知道
[
2
,
3
]
[2,3]
[2,3]区间的和为5,呢么我们想求
[
1
,
4
]
[1,4]
[1,4]区间的和怎么办? 明显我们把已知区间通过扩大或者缩小左右边界就能得到我们想要的值
s
u
m
(
[
1
,
4
]
)
=
s
u
m
(
s
u
m
[
2
,
3
]
+
a
[
−
−
l
]
+
a
[
+
+
r
]
)
sum([1,4])=sum(sum[2,3]+a[--l]+a[++r])
sum([1,4])=sum(sum[2,3]+a[−−l]+a[++r]),但是有一个坑点,就是如果我们询问的区间之间的间隔很大,呢么时间复杂度就炸了,举个栗子:我们第一次要询问
[
1
,
1
]
[1,1]
[1,1]区间的和,第二次询问
[
n
,
n
]
[n,n]
[n,n],第三次询问
[
1
,
1
]
[1,1]
[1,1]…询问m次,呢么每次移动从
[
1
,
1
]
[1,1]
[1,1]移动到
[
n
,
n
]
[n,n]
[n,n],就要移动2*n次,呢么m次询问就是
O
(
n
∗
m
)
O(n*m)
O(n∗m),肯定顶不住了,呢么莫队算法就解决了这个问题,从而把时间复杂度优化到了
O
(
n
s
q
r
t
(
n
)
)
O(nsqrt(n))
O(nsqrt(n)),其实原理很简单,就是排序,把区间排个序,这样就不会出现上面那种情况了,这也是莫队操作强制离线的原因。我们只需要记录求的区间的id,把答案按照原序输出即可
莫对区间的排序:
排序才是莫队算法的精髓,而且这个需要用到分块,如果想细致了解的话就去看看分块,如果不想细致了解,就记住排序的方式,这也是莫队被人称为板子的原因,因为莫队除了写出增缩区间的策略,其他的其实一毛一样。
排序的方式:
如果询问区间的l在同一个块中,就按照询问区间的r排序从小到大的序,如果不在同一个块中,就按照l所在块的大小,从小到大排序
为什么这样排:
其实很简单,优先排l所在块小的这样,我们把l块小的增缩完后,就不会在回到块小的,同理在同一块中按照r小的排,把r小的增缩完后,他就不会在回到后面了,而是往前,这也时间复杂度也就 O ( n + m ) O(n+m) O(n+m)
讲完这个,这题就很简单了,我们只需要考虑增缩的方案,首先我们如果要往区间中加贡献,呢么我们只需要考虑当前的a[i]的值于它的个数之间的关系,如果本身等于,呢么我们加后,这个区间的答案就由上个区间的答案–,如果++后等于a[i]的值,呢么这个区间的答案就有上个区间的答案++,同理减贡献也一样
这题的话需要离散化,因为数很大,而且离散化后数的数量并不影响结果,所以可以使用离散化,而且map会t,因为在增缩操作是 O ( n + m ) O(n+m) O(n+m)这样导致的时间复杂度是 O ( n + m ) l o g n O(n+m)logn O(n+m)logn,所以我们可以另开一个数组进行辅助,这也就可以把时间复杂度降到 O ( n l o g n ) O(nlogn) O(nlogn)
其实1e5这个数据量刚好被莫队水过了,如果数据量再大一点,就不行了,这也是上面为什么不能用map的原因,可以算一下 1 e 5 ∗ s q r t ( 1 e 5 ) = 2 e 7 1e5*sqrt(1e5)=2e7 1e5∗sqrt(1e5)=2e7,刚刚好蹭着时间复杂度的边过,很危险
参考代码:
#include <cstdio>
#include <algorithm>
#include <vector>
#include <iostream>
#include <map>
#include <queue>
#include <cstring>
#include <string>
#include <cstdlib>
#include <cmath>
using namespace std;
const int inf = 1 << 30;
const int maxn = 1e5 + 5;
typedef long long ll;
const int N = 5005;
int dp[N], ans[maxn];
struct node
{
int l, r, id;
} p[maxn << 1];
int pos[maxn], vis[maxn];
vector<int> vec;
int getid(int x)
{
return lower_bound(vec.begin(), vec.end(), x) - vec.begin() + 1;
}
bool cmp(node a, node b)
{
if (pos[a.l] == pos[b.l])
return a.r < b.r;
return pos[a.l] < pos[b.l];
}
int res, a[maxn], b[maxn];
void add(int i)
{
vis[b[i]]++;
if (vis[b[i]] == a[i])
res++;
else if (vis[b[i]]-1 == a[i])
res--;
}
void sub(int i)
{
if (vis[b[i]] == a[i])
res--;
else if (vis[b[i]] == a[i] + 1)
res++;
vis[b[i]]--;
}
int main()
{
//ios::sync_with_stdio(false);
//cin.tie(0);
int n, q;
cin >> n >> q;
int dis = sqrt(n);
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]), pos[i] = i / dis, vec.push_back(a[i]);
sort(vec.begin(), vec.end());
vec.erase(unique(vec.begin(), vec.end()), vec.end());
for (int i = 1; i <= n; i++)
b[i] = getid(a[i]);
for (int i = 0; i < q; i++)
{
scanf("%d %d", &p[i].l, &p[i].r);
p[i].id = i;
}
sort(p, p + q, cmp);
int l = 1, r = 0;
for (int i = 0; i < q; i++)
{
while (r < p[i].r)
add(++r);
while (r > p[i].r)
sub(r--);
while (l < p[i].l)
sub(l++);
while (l > p[i].l)
add(--l);
ans[p[i].id] = res;
}
for (int i = 0; i < q; i++)
printf("%d\n", ans[i]);
}