https://vjudge.net/problem/SPOJ-DQUERY
离线+树状数组
先离线下,对询问的r排序,以元素的下标作树状数组维护以r为右边界的区间不同元素的数量,遍历时如果当前元素没有出现,那么存在他的地址,并在树状数组对应下标+1,如果这个元素
之前已经出现过了,那么取消之前标记的点也就是将这个元素上一次出现的下标在树状数组中-1,变成0,然后再储存下当前元素最迟出现的下标,也就是当前点。
最后区间[l,r]之间不同的数量也就是sumR - sumL;
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e6+10;
int vis[maxn];
int a[maxn],c[maxn],n,ans[maxn],m;
int lowbit(int x){
return x&-x;
}
int getsum(int x) {
int sum = 0;
while (x) {
sum += c[x];
x -= lowbit(x);
}
return sum;
}
void add(int x,int val) {
while (x <= n) {
c[x] += val;
x += lowbit(x);
}
}
struct node{
int l,r,id;
bool operator<(const node &b)const {
return r<b.r;
}
}q[maxn];
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
scanf("%d", &m);
for (int i = 1; i <= m; i++) {
scanf("%d%d", &q[i].l, &q[i].r);
q[i].id = i;
}
sort(q + 1, q + 1 + m);
int l = 1;
for (int i = 1; i <= m; i++) {
for (int j = l; j <= q[i].r; j++) {
if (vis[a[j]]) {
add(vis[a[j]], -1);
}
add(j, 1);
vis[a[j]] = j;
}
l = q[i].r + 1;
ans[q[i].id] = getsum(q[i].r) - getsum(q[i].l - 1);
}
for (int i = 1; i <= m; i++) {
printf("%d\n", ans[i]);
}
}
莫队解法:
思路:这道题感觉就是直接莫队莽上就完事了,没有什么需要处理的,就维护下每个元素在当前区间出现的次数就好了。
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e6+10;
int a[maxn],n,num[maxn],m,ans,block,vis[maxn];
struct node {
int l, r, id;
bool operator<(const node &b) const {
if (l / block == b.l / block) return r < b.r;
return l / block < b.l / block;
}
}q[maxn];
void add(int x){
vis[a[x]]++;
if (vis[a[x]]==1)ans++;
}
void del(int x){
vis[a[x]]--;
if (vis[a[x]]==0)ans--;
}
int main() {
scanf("%d", &n);
block=sqrt(n);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
scanf("%d", &m);
for (int i = 1; i <= m; i++) {
scanf("%d%d", &q[i].l, &q[i].r);
q[i].id = i;
}
sort(q + 1, q + 1 + m);
int l = 1,r=0;
for (int i = 1; i <= m; i++) {
while (l<q[i].l) {
del(l);
l++;
}
while (l>q[i].l){
l--;
add(l);
}
while (r<q[i].r){
r++;
add(r);
}
while (r>q[i].r){
del(r);
r--;
}
num[q[i].id] =ans;
}
for (int i = 1; i <= m; i++) {
printf("%d\n", num[i]);
}
}
主席树解法:
我们将元素在序列中的地址建成主席树,如果这个元素没有出现过,那么就在这个位置上+1,如果这个元素出现过那就将这个元素上一次出现的位置-1,在当前位置+1;每棵线段树维护的都是1到当前位置的不同元素的个数。
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e6+10;
int a,n,root[maxn],m,vis[maxn],cnt,ret;
struct node
{
int l,r,sum;
} hjt[maxn*40];
void insert(int l,int r,int pre,int &now,int p,int c)
{
hjt[++cnt]=hjt[pre];
now=cnt;
hjt[now].sum+=c;
if (l==r)
{
return;
}
int mid=(l+r)>>1;
if (p<=mid)
{
insert(l,mid,hjt[pre].l,hjt[now].l,p,c);
}
else
{
insert(mid+1,r,hjt[pre].r,hjt[now].r,p,c);
}
}
void query(int k,int l,int r,int p) {
if (p == l) {
ret += hjt[k].sum;
return;
}
int mid = (l + r) >> 1;
if (p <= mid) {
ret += hjt[hjt[k].r].sum;
query(hjt[k].l, l, mid, p);
} else query(hjt[k].r, mid + 1, r, p);
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &a);
int tmp = 0;
if (!vis[a]) insert(1, n, root[i - 1], root[i], i, 1);
else {
insert(1, n, root[i - 1], tmp, vis[a], -1);
insert(1, n, tmp, root[i], i, 1);
}
vis[a] = i;
}
scanf("%d", &m);
for (int i = 1, l, r; i <= m; i++) {
scanf("%d%d", &l, &r);
ret = 0;
query(root[r], 1, n, l);
printf("%d\n", ret);
}
}