题意:
1.给你一个n个数的数列和q次询问l, r,需要回答a[1]…a[l], a[r]…a[n]的不同数的个数。
n,q <=1e5,a[i] <= n
固定块大小的莫队会TLE
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MOD = 1e9+7;
const int maxn = 1e6 + 10;
const int inf = 0x3f3f3f3f;
#define met(a, b) memset(a, b, sizeof(a))
struct MATION {
int l, r, id;
} q[2 * maxn];
ll cnt = 0;
int pos[maxn], block, val[maxn], ANS[maxn], a[maxn];
bool vis[maxn];
int d, n, m, top = 0;
int cmp(MATION a, MATION b) {
if(pos[a.l] != pos[b.l]) return pos[a.l] < pos[b.l];
else return a.r < b.r;
}
void solve() {
met(val, 0);
int ans = 0;
int l = q[0].l, r = q[0].l - 1;
for(int i = 0; i < top; i++) {
while(l < q[i].l) {
val[a[l]]--;
if(val[a[l]] == 0) ans--;
l++;
}
while(l > q[i].l) {
l--;
val[a[l]]++;
if(val[a[l]] == 1) ans++;
}
while(r < q[i].r) {
r++;
val[a[r]]++;
if(val[a[r]] == 1) ans++;
}
while(r > q[i].r) {
val[a[r]]--;
if(val[a[r]] == 0) ans--;
r--;
}
ANS[q[i].id] = ans;
}
}
int main() {
while(~scanf("%d%d", &n, &m)) {
met(vis, false);
cnt = 0;
block = sqrt(n);
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
pos[i] = (i - 1) / block + 1;
pos[i + n] = (i + n - 1) / block + 1;
a[i + n] = a[i];
if(!vis[a[i]]) {
vis[a[i]] = true;
cnt++;
}
}
int l, r;
top = 0;
for(int i = 0; i < m; i++) {
scanf("%d%d", &l, &r);
if(r <= l) {
ANS[i] = cnt;
continue;
}
q[top].l = r;
q[top].r = n + l;
q[top].id = i;
top++;
}
sort(q, q + top, cmp);
solve();
for(int i = 0; i < m; i++) {
//cout << ANS[i * 2] << " * " << ANS[i * 2 + 1] << endl;
printf("%d\n",ANS[i]);
}
}
return 0;
}
- 普通树状数组做法
原做法基本操作讲解 http://www.cnblogs.com/chenquanwei/articles/9340577.html
将数组复制一遍即可 https://blog.csdn.net/Coldfresh/article/details/81122267
(标程做法)
加一个first数组需要转化:https://blog.csdn.net/c6376315qqso/article/details/81130241
转化思路 :转化为求n- (a[1]-a[l] +a[r]-a[n])没出现过得数的个数
即在a[l+1]-a[r]出现过 而在其他区间没出现过得数的个数
即加一个first数组
之前是add(last[a[i]]]) 求的是在本区间出现过得数的个数但是本区间出现过得数其实是有可能在其他区间再次出现的,而加了first数组之后,因为for循环,断绝了a[r]右边的区间,又因为first断绝了这些数在a[l]左边出现的情况。(如果在a[l]左边出现,则不会被统计到a[l],a[r]区间内)
拓展题:https://blog.csdn.net/alan_cty/article/details/52810570
给出n个数,m次询问,每次询问区间[l,r]中出现次数为偶数的数的异或和。
我们通过求出区间内不同的数(每个数只有一个)的异或和^(区间内数的整体异或和即区间内出现奇数次数的异或和即可求出答案),而关键在于求出区间内不同的数(每个数只有一个)的异或和,这里我们就得再次用到上文中树状数组那个方法里。只不过这里树状数组要更换成异或版的。
#include<bits/stdc++.h>
#define fir first
#define sec second
#define pii pair<int,int>
#define debug(x) cout<<#x<<" = "<<x<<endl;
using namespace std;
typedef long long ll;
const int MOD = 1e9 + 7;
const int MAXN = 1e5 + 5;
int fir[MAXN], last[MAXN];
int tree[MAXN];
int n, q;
struct Query {
int l, r;
bool operator < (const Query & x) const {
return r < x.r;
}
int id;
} que[MAXN];
void add(int i, int v) {
while(i <= n) {
tree[i] += v;
i += i&-i;
}
}
int query(int i) {
int ret = 0;
while(i) {
ret += tree[i];
i -= i&-i;
}
return ret;
}
int ans[MAXN];
int a[MAXN];
int main() {
#ifdef LOCAL
freopen("input.txt", "r", stdin);
#endif
while(~scanf("%d %d", &n, &q)) {
memset(fir, 0, sizeof(fir));
memset(tree, 0, sizeof(tree));
int tot = 0;
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
if(!fir[a[i]]) fir[a[i]] = i, tot++;
last[a[i]] = i;
}
for(int i = 1; i <= q; i++) scanf("%d %d", &que[i].l, &que[i].r), que[i].id = i;
sort(que + 1, que + 1 + q);
int p = 1;
for(int i = 1; i <= n; i++) {
while(p <= q && que[p].r == i) {
int id = que[p].id;
ans[id] = tot - query(i) + query(que[p].l);
p++;
}
if(last[a[i]] == i) add(fir[a[i]], 1);
}
for(int i = 1; i <= q; i++) printf("%d\n", ans[i]);
}
}