传送门

  • 题意
    求区间\([l,r]\),值在\([a,b]\)的数量以及值的数量。

  • 思路1:莫队+分块
    套树状数组的莫队的修改是\(n\sqrt{n}log_n\),查询是\(nlogn\),并不平均。
    如果查询的时候按值域分块(移动\(l/r\)端点框区间,维护每个块内值当前的个数(整块),以及每个值的个数(散块)),即修改和查询都是\(n\sqrt{n}\)了。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
#define fi first
#define se second
typedef pair<int,int> PII;
int n, m, B[N], Blen = 300, val[N], b[N], Btot;
PII ans[N];
struct node {int l, r, a, b, id;}Q[N];
bool cmp(node u, node v) {return B[u.l] == B[v.l] ? u.r < v.r : B[u.l] < B[v.l];}
int tl, tr, c1[N], b1[N], b0[N], L[N], R[N];
void Add(int x) {b1[B[x]]++; if(!c1[x]++) b0[B[x]]++;}
void Del(int x) {b1[B[x]]--; if(!--c1[x]) b0[B[x]]--;}
PII Query(int l, int r) {
	PII res = make_pair(0, 0);
	if(B[l] == B[r]) {
		for(int i = l; i <= r; i++) if(c1[i]) {res.fi += c1[i]; res.se++;} 
		return res;
	}
	for(int i = l; i <= R[B[l]]; i++) if(c1[i]) {res.fi += c1[i]; res.se++;}
	for(int i = L[B[r]]; i <= r; i++) if(c1[i]) {res.fi += c1[i]; res.se++;}
	for(int i = B[l] + 1; i < B[r]; i++) {res.fi += b1[i]; res.se += b0[i];}
	return res;
}
void solve() {
	tl = 1, tr = 0;
	sort(Q + 1, Q + 1 + m, cmp);
	for(int i = 1; i <= m; i++) {
		int l(Q[i].l), r(Q[i].r);
		if(Q[i].a > Q[i].b) {ans[Q[i].id] = make_pair(0, 0); continue;}
		while(tl > l) {Add(b[--tl]);}
		while(tr < r) {Add(b[++tr]);}
		while(tl < l) {Del(b[tl++]);}
		while(tr > r) {Del(b[tr--]);}
		ans[Q[i].id] = Query(Q[i].a, Q[i].b);
	}
}
int main() {
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= n; i++) {scanf("%d", &b[i]); val[i] = b[i];}
	sort(val + 1, val + 1 + n);
	int tt = unique(val + 1, val + 1 + n) - val;
	for(int i = 1; i <= n; i++) b[i] = lower_bound(val + 1, val + tt, b[i]) - val;
	for(int i = 1; i <= m; i++) {
		scanf("%d%d%d%d", &Q[i].l, &Q[i].r, &Q[i].a, &Q[i].b); Q[i].id = i;
		Q[i].a = lower_bound(val + 1, val + tt, Q[i].a) - val;
		Q[i].b = upper_bound(val + 1, val + tt, Q[i].b) - val - 1;
	}
	for(int l = 1, r; l <= n; l = r + 1) {
		++Btot;
		r = min(n, l + Blen - 1);
		L[Btot] = l, R[Btot] = r;
		for(int i = l; i <= r; i++) B[i] = Btot;
	} 
	solve();
	for(int i = 1; i <= m; i++) {printf("%d %d\n", ans[i].fi, ans[i].se);}
	return 0;
}

  • 思路2:容斥+cdq(三维偏序)
    值的个数,可以钦定\(pre_i\)表示\(i\)以前第一个和\(a_i\)相等的位置,只统计区间内一个值第一次出现的即可。对于一个询问,查的就是满足:
    \(l\le x \le r\)\(a\le a_x\le b\)\(pre_x<l\)
    前两个限制有上下界,可以容斥拆成四个限制。
    cdq要非常注意的是排序,当前按\(x\)排,但也要保证后两位比较时小的在大的前面,最后一个关键字不用排(正常来说排也没有问题,但这道题一个询问的第一问是没有第三个关键字限制的),直接替换为按操作放在询问前面就可以了(自然每个询问前面都有所有操作)。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define lowbit(x) x & (-x)
const int N = 1e6 + 5;
const int M = 5e6 + 5;
int lst[N], a[N], tot, ans1[N], ans2[N], n, m;
struct data {int opt, x, y, z, id;} Q[M], tmp[M];
bool cmpx(data u, data v) {return u.x ^ v.x ? u.x < v.x : (u.y ^ v.y ? u.y < v.y : (u.id ^ v.id ? u.id < v.id : u.z < v.z));}
bool cmpy(data u, data v) {return u.y ^ v.y ? u.y < v.y : (u.id ^ v.id ? u.id < v.id : u.z < v.z);}

int c[N], sum = 0;
void Update(int x) {sum++; for(; x <= n; x += lowbit(x)) c[x]++;}
int Ask(int x) {int res(0); for(; x; x -= lowbit(x)) res += c[x]; return res;}
void Clear(int x) {for(; x <= n; x += lowbit(x)) c[x] = 0;}

void CDQ(int l, int r) {
	if(l == r) return;
	int mid = (l + r) >> 1;
	CDQ(l, mid), CDQ(mid + 1, r);
	int tl = l, tr = mid + 1, tt = l;
	while(tt <= r) {
		if(tr > r || (tl <= mid && cmpy(Q[tl], Q[tr]))) {
			if(!Q[tl].opt) {Update(Q[tl].z);}
			tmp[tt++] = Q[tl++];
		}
		else {
			if(Q[tr].opt) {
				ans1[Q[tr].id] += Q[tr].opt * sum; ans2[Q[tr].id] += Q[tr].opt * Ask(Q[tr].z);
			}
			tmp[tt++] = Q[tr++];
		}
	}
	sum = 0;
	for(int i = l; i <= mid; i++) if(!Q[i].opt) Clear(Q[i].z);
	for(int i = l; i <= r; i++) Q[i] = tmp[i];
}
int main() {
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= n; i++) {
		scanf("%d", &a[i]);
		Q[++tot] = (data){0, i, a[i], lst[a[i]] + 1, 0};
		lst[a[i]] = i;
	}
	for(int i = 1; i <= m; i++) {
		int l, r, a, b;
		scanf("%d%d%d%d", &l, &r, &a, &b);
		Q[++tot] = (data){1, r, b, l, i}, Q[++tot] = (data){1, l - 1, a - 1, l, i};
		Q[++tot] = (data){-1, r, a - 1, l, i}, Q[++tot] = (data){-1, l - 1, b, l, i};
	}
	sort(Q + 1, Q + 1 + tot, cmpx);
	CDQ(1, tot);
	for(int i = 1; i <= m; i++) {printf("%d %d\n", ans1[i], ans2[i]);}
	return 0;
}
//5 1
//1 5 4 2 2 
//2 5 1 2

实测cdq跑得慢一些(跟\(5m\)相关)