1 条题解

  • 0
    @ 2026-4-30 20:41:18

    题意

    给定一个数组 AA,有多个查询 [l,r][l, r],需要判断每个查询区间内是否存在一个数值 xx,使得该数值在区间内恰好出现 xx 次。

    思路

    本题可以用更简单的 O(NN)O(N \sqrt{N}) 解法,但这里将介绍一种 O(NlogN)O(N \log N) 的离线算法。
    我们将所有查询离线处理。对于每个右端点 xx0x<n0 \le x < n),记录所有以 xx 结尾的查询。然后从左到右遍历 xx,同时维护一个数组 DD,使得对于当前 xxDl+Dl+1++DxD_l + D_{l+1} + \dots + D_x 就是查询 [l,x][l, x] 的答案。
    为了保持 DD 的正确性,在处理所有以 xx 结尾的查询之前,需要先更新 DD。设当前数值 t=Axt = A_x,并设向量 PP 为数值 tt 之前出现位置的下标列表(下标从 00 开始)。

    • 如果 Pt|P| \ge t,则需要在 DP[Pt]D_{P[|P|-t]} 上加 11,因为这个位置是从右往左第一个恰好包含 tttt 的位置。
    • 之后,如果 P>t|P| > t,则需要在 DP[Pt1]D_{P[|P|-t-1]} 上减 22,以关闭当前区间并取消之前的贡献。
    • 最后,如果 P>t+1|P| > t+1,则还需要在 DP[Pt2]D_{P[|P|-t-2]} 上加 11,以取消之前对区间的关闭操作。

    算法步骤

    1. 将所有查询按右端点 rr 分组,存入列表。
    2. 初始化数组 DD 全为 00,并初始化每个数值的出现位置列表 PP 为空。
    3. 从左到右遍历 xx00n1n-1
      • t=Axt = A_x,将 xx 加入 tt 对应的位置列表 PP
      • 根据上述规则更新 DD 数组:
        • Pt|P| \ge t,则 DP[Pt]DP[Pt]+1D_{P[|P|-t]} \leftarrow D_{P[|P|-t]} + 1
        • P>t|P| > t,则 DP[Pt1]DP[Pt1]2D_{P[|P|-t-1]} \leftarrow D_{P[|P|-t-1]} - 2
        • P>t+1|P| > t+1,则 DP[Pt2]DP[Pt2]+1D_{P[|P|-t-2]} \leftarrow D_{P[|P|-t-2]} + 1
      • 对于所有以 xx 为右端点的查询 [l,x][l, x],计算 Dl+Dl+1++DxD_l + D_{l+1} + \dots + D_x 作为答案。
    4. 输出所有查询的答案。

    复杂度分析

    • 时间复杂度:O(NlogN)O(N \log N),其中 NN 为数组长度。每次更新 DD 和查询区间和均可用树状数组或线段树实现 O(logN)O(\log N) 操作。
    • 空间复杂度:O(N)O(N),用于存储 DD 数组、位置列表和查询分组。

    代码说明

    • 使用树状数组(Fenwick Tree)维护 DD 的区间和,支持单点更新和前缀和查询。
    • 遍历 xx 时,对每个数值 tt 维护其出现位置列表,按上述规则进行三次更新。
    • 对于每个查询 [l,x][l, x],通过树状数组计算 sum(x)sum(l1)sum(x) - sum(l-1) 得到答案。
    #include <cstdio>
    
    struct { inline operator int () { int x; return scanf("%d", &x), x; } } read;
    
    const int maxn = 100005, maxb = 500;
    int a[maxn], tot[maxn];
    int t[maxb][maxn], val[maxb];
    
    int main () {
    	int n = read, q = read;
    	for (int i = 1; i <= n; i ++)
    		if ((a[i] = read) <= n)
    			++ tot[a[i]];
    	int p = 0;
    	for (int x = 1; x <= n; x ++)
    		if (tot[x] >= x) {
    			val[++ p] = x;
    			for (int i = 1; i <= n; i ++)
    				t[p][i] = t[p][i - 1] + (a[i] == x);
    		}
    	while (q --) {
    		int l = read, r = read, ans = 0;
    		for (int i = 1; i <= p; i ++)
    			if (t[i][r] - t[i][l - 1] == val[i])
    				++ ans;
    		printf("%d\n", ans);
    	}
    }
    
    
    
    • 1

    信息

    ID
    6720
    时间
    1000ms
    内存
    256MiB
    难度
    10
    标签
    递交数
    2
    已通过
    1
    上传者