题目大意是给出n个数,对于每次查询(l,r),求出(1,l)和(r,n)两个区间不同的数有几个。
第一步,先考虑把两个区间的问题,化成一个区间内不同数的个数:
把原数组复制一遍贴到原数组的末尾,这样数组长度变成2n,查询区间变成了(r,n+l)。
接着,对于如何求一个区间内不同数的个数,可以使用树状数组进行维护。看得懂模板,会使用即可。
有关树状数组:https://blog.youkuaiyun.com/rentenglong2012/article/details/69230308
https://blog.youkuaiyun.com/flushhip/article/details/79165701
树状数组模板:
class TA//树状数组模板
{
private:
int len;
int lb(int k) //取某个数二进制表示中最后一个1
{
return k&(-k);//or int t=k ; t&=(t-1); k=k-t;
}
public:
int e[N];
void add(int x, int v)//加元素
{
while (x <= len)
{
e[x] += v;
x += lb(x);
}
}
void init(int* getin, int _len)//数组初始化
{
len = _len;
for (int i = 1; i <= len; i++)
{
add(i, *(getin + i));
}
}
int query(int x)//查询0到x的区间和
{
int sum = 0;
while (x>0)
{
sum += e[x];
x -= lb(x);
}
return sum;
}
};
主要用两个数组first(标记数第一次出现的位置)和nxt(存这个数下一次出现的位置);
从1到2n遍历一遍,初始化first和nxt,并将first存入树状数组中,区间之间除了0就是1,所以区间中不同整数的个数便是树状数组的区间和。
接着对于q个查询区间进行升序排序。
从1遍历到第一个查询区间的左端点,过程中,对于first[i]=1,将first[i]变为0,first[nxt[i]]变为1,并更新树状数组。
计算区间和,保存结果,接着从第一个左端点遍历到第二个左端点,重复以上操作 ,直至q次操作完。
AC代码:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 2e5 + 10;
struct Q {
int l, r, id;
bool operator < (const Q &b) const {
return this->l < b.l;
}
}qry[N];
class TA//树状数组模板
{
private:
int len;
int lb(int k) //取某个数二进制表示中最后一个1
{
return k&(-k);//or int t=k ; t&=(t-1); k=k-t;
}
public:
int e[N];
void add(int x, int v)//加元素
{
while (x <= len)
{
e[x] += v;
x += lb(x);
}
}
void init(int* getin, int _len)//数组初始化
{
len = _len;
for (int i = 1; i <= len; i++)
{
add(i, *(getin + i));
}
}
int query(int x)//查询0到x的区间和
{
int sum = 0;
while (x>0)
{
sum += e[x];
x -= lb(x);
}
return sum;
}
};
int a[N];
int last[N];//标记数上一次出现的位置,辅助计算nxt;
int first[N];//标记数第一次出现的位置
int vis[N];//判断数字是否第一次出现;
int nxt[N];//存数下一次出现的位置
int ans[N];//保存查询结果;
int main()
{
int n, q;
TA acm;
while (scanf("%d %d", &n, &q) == 2)
{
memset(last, 0, sizeof(last));//数组初始化;
memset(first, 0, sizeof(first));
memset(vis, 0, sizeof(vis));
memset(nxt, -1, sizeof(nxt));
memset(acm.e, 0, sizeof(acm.e));
for (int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
a[i + n] = a[i];//数组增加,两个区间变一个区间;
}
n *= 2;
//遍历赋值first数组和nxt数组;
for (int i = 1; i <= n; i++) {
if (!vis[a[i]])
{
vis[a[i]] = 1;
first[i] = 1;
}
nxt[last[a[i]]] = i;
last[a[i]] = i;
}
for (int i = 0; i<q; i++)//输入查询区间
{
scanf("%d %d", &qry[i].l, &qry[i].r);
qry[i].l += n / 2;//l=l+n;
swap(qry[i].l, qry[i].r);//交换l,r位置;
qry[i].id = i;//标记;
}
acm.init(first, n);//将first数组弄成树状数组;
int now = 1;
sort(qry, qry + q);//将查询区间排序;
for (int i = 0; i<q; i++)
{
while (now<qry[i].l)//更新树状数组
{
if (first[now])
{
first[now] = 0;
acm.add(now, -1);
first[nxt[now]] = 1;//更新1的位置
acm.add(nxt[now], 1);
}
now++;
}
//cout<<acm.query(qry[i].r)<<" "<<acm.query(qry[i].l-1)<<endl;
ans[qry[i].id] = acm.query(qry[i].r) - acm.query(qry[i].l - 1);//算区间之间有几个1;
}
for (int i = 0; i<q; i++)
{
printf("%d\n", ans[i]);
}
}
return 0;
}