题目大概是给你一个串和l和r 找到1~l r~n里面不同的数字的个数
但是串很长,询问次数也很多,都有1e5,所以需要一些技巧:
(1)显然,把区间增大一倍,去查询(r,l+n)更好。
(2)怎么快速找到(x,y), 不同的数字的个数?
1.每个区间里面不同的个数维护一个sum[i] (似乎可以用set)
这样,sum[y]-sum[x-1]就是,在区间(x,y)出现了但是(1,x-1)没有出现的数字的个数
(没错吧!!! 因为那些只会有了新数字才进行更新)
2.再找到,(1,x-1)和(x,y)区间相同的数字的个数
我们可以知道,除了新增的,就是(x,y)区间里面本来就有过了重合的。
那么第二问怎么求呢?欢迎看一下下面的图。
【这个大概就是离线的做法 】
树状数组维护的是g[i]的和
www... nxt是它们下一次出现的位置,记录一下就好
然后呢,g[i]是贡献值,其实也就是你下一次出现在哪里,哪里就=1
(大概是的)最后求一下这个区间里的和就是他们相同的数字出现的次数了, 只是,需要按每个查询顺序更新,不然可能会出点毛病?
其实想想也非常合理,前面所有的数字在后面出现了的话后面的地方 就置1,这样就是前面的在后面的一个影子出现了呀,
哪怕是(3 * **** 3 ***3),前面有个3 (更新了第二个3的位置),第二个3更新了第三个3的位置
1~(x-1)是很长的一段啊,没毛病,没毛病。。
这个数字没出现的话就没有影子~
参考文章:找到一篇链接,这写的可真好啊
点击这里 巴布巴布~ (下图来自链接)
需要什么内容呢
比如我们想一下,(7,9)的时候去更新一下(1,6)的,然后) 换到(8,12)7那边+1, 这样10的位置也是1了(更新移动经过的位置~) 这样查询的和就是3了,还是很棒的,求和是3再加上原来的sum咯
(以上两图为转载,出处 这里)
我的代码:(待续
--------------从前是线段树求最大值最小值,只要add和q一下就可以了
线段树也可以用来求区间的和:(下面是出处代码的解释)
sum是只增加不减少的,所以完全不用set,结合last用也可以,last是上一次这个元素出现的位置,那么:
1.last[a[i]] = i; a[i]上一次出现的位置是xxx如果没出现过就更新sum
2.nxt[last[a[i]]] = i;
(在上一行运算之前)
比如2先在3出现,然后在5出现,随后在7出现
然后last[2]=3
这次其实a[i]=2, last[2]=3 nxt[3]=5
and after that
last[2]=5
然后
a[i]=2( i=5)
last[2]=5, 更新 nxt[5]=7
last[2]=7……
这样我们也有了last数组(不这个没用)
我们就有了nxt数组,还有sum数组
剩下的是线段树里面的东西,g[i] 在线段树里内部的,,,, 方便理解了
g[i]是根据nxt来的嘛,nxt有的话就添加一下,询问的时候还是l,r,1,1,n
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cmath>
#include<string>
#include<cstring>
#include<vector>
using namespace std;
#define ll long long
#define lson id<<1
#define rson id<<1|1
const int maxn = 2e5 + 10;
struct question {
int l, r, id;
}q[maxn];
int a[maxn];
int sum[maxn]; //用于记录1~i中存在多少种数
int last[maxn]; //用于记录每种数最后出现的位置(单纯为nxt数组服务)
int nxt[maxn]; //用于记录每个数下一个相同数的位置
int tre[maxn<<2]; //线段树的结果
int ans[maxn]; //用于保存问题的答案
int n, m;
bool cmp(question aa, question bb) {
return aa.l < bb.l;
}
void push(int id) { //更新
tre[id] = tre[lson] + tre[rson];
}
void add(int L, int R, int x, int l, int r, int id) {//区间[L,R]的值+x
if (L <= l&&r <= R) //l和r是当前边界,id是当前id
{
tre[id] += x;
return;
}
int mid = (l + r) >> 1;
if (L <= mid) add(L, R, x, l, mid, lson);
if (mid < R) add(L, R, x, mid + 1, r, rson);
push(id);
}
int query(int L, int R, int l, int r, int id) {//查询区间[L,R]的结果
if (L <= l&&r <= R)
{
return tre[id];
}
int mid = (l + r) >> 1;
int s1 = 0, s2 = 0;
if (L <= mid) s1 = query(L, R, l, mid, lson);
if (mid < R) s2 = query(L, R, mid + 1, r, rson);
return s1 + s2;
}
void input() { //输入
int l, r;
for (int i = 1; i <= n; i++) { //倍增
scanf("%d", &a[i]);
a[i + n] = a[i];
}
for (int i = 0; i < m; i++) { //修改每个询问为[R,L+N]
scanf("%d%d", &l, &r);
q[i].l = r;
q[i].r = l + n;
q[i].id = i;
}
}
void work() { //处理
n <<= 1;
sum[0] = 0;
for (int i = 1; i <= n; i++) {
if (last[a[i]] == 0) //第一次出现的元素
sum[i] = sum[i - 1] + 1;
else {
sum[i] = sum[i - 1];
nxt[last[a[i]]] = i; //更新上一次出现的nxt值
}
last[a[i]] = i;
}
sort(q, q + m, cmp); //左边界升序排序
int x = 1; //维护的X值表示1~X都为一扫描区间
for (int i = 0; i < m; i++) {
while (x < q[i].l) { //更新X同时对每个元素下个元素不为空值得情况更新
if (nxt[x])
add(nxt[x], nxt[x], 1, 1, n, 1);
x++;
} //计算该询问的答案
ans[q[i].id] = sum[q[i].r] - sum[q[i].l - 1] + query(q[i].l, q[i].r, 1, n, 1);
}
}
int main() {
while (~scanf("%d%d", &n, &m)) {
memset(last, 0, sizeof(last));
memset(nxt, 0, sizeof(nxt));
memset(tre, 0, sizeof(tre));
input();
work();
for (int i = 0; i < m; i++)//输出
printf("%d\n", ans[i]);
}
return 0;
}
方法很巧妙,思想和写的也很巧妙
我们看另一份代码,求和的话可能树桩数组更好一点:
基础思想是不变的,sum分开求好理解一些。
这份好理解了很多…………
cmd排序,再排序输出,树状数组更新的时候要n,,,,。。。
#include<bits/stdc++.h>
using namespace std;
const int maxn=200000+10;
const int inf=0x3f3f3f3f;
#define ll long long
struct P {
int l,r;
int id;
int ans;
} p[2*maxn];
bool cmd(P a,P b) {
return a.l<b.l;
}
bool cmd1(P a,P b) {
return a.id<b.id;
}
/********树状数组*/
int lowbit(int x) {
return x&(-x);
}
int c[maxn];
int sum(int i) {
int s=0;
while(i>0) {
s+=c[i];
i-=lowbit(i);
}
return s;
}
void add(int i,int val,int n) {
if(i==0)
return;
while(i<=n) {
c[i]+=val;
i+=lowbit(i);
}
return;
}
int query_sum(int l,int r){ //求树状数组区间[l,r]的和
return sum(r)-sum(l-1);
}
/*********/
int n,m;
int pre[2*maxn]; //pre[i]表示区间[1,i]之间有多少个不同的数字
int last[maxn],nxt[maxn]; //last为辅助数组,为了求nxt[],nxt[]表示每一个点的数值下一次出现的位置
int num[2*maxn]; //输入的数组
bool vis[maxn]; //辅助数组
void init(int n) { //预处理出pre[]和nxt[]
memset(vis,0,sizeof(vis));
memset(pre,0,sizeof(pre));
memset(last,-1,sizeof(last));
memset(nxt,0,sizeof(nxt));
for(int i=1; i<=n; i++) {
if(!vis[num[i]]) {
pre[i]=pre[i-1]+1;
vis[num[i]]=true;
}
else
pre[i]=pre[i-1];
}
for(int i=1; i<=n; i++) {
if(last[num[i]]==-1) {
last[num[i]]=i;
} else {
int u=last[num[i]];
nxt[u]=i;
last[num[i]]=i;
}
}
return;
}
void solve(int n,int m) { //求m次询问的结果
memset(c,0,sizeof(c));
int top=0;
for(int i=1; i<=m; i++) {
int l=p[i].l;
int r=p[i].r;
for(top; top<l; top++) {
add(nxt[top],1,2*n);
}
int u=sum(r);
int v=sum(l-1);
p[i].ans=pre[r]-pre[l-1]+query_sum(l,r);
}
return;
}
void debug(int n){
for(int i=1;i<=2*n;i++){
printf("%d%c",pre[i],i==2*n?'\n':' ');
}
for(int i=1;i<=2*n;i++){
printf("%d%c",nxt[i],i==2*n?'\n':' ');
}
for(int i=1;i<=m;i++){
printf("%d %d\n",p[i].l,p[i].r);
}
}
int main() {
while(~scanf("%d %d",&n,&m)) {
for(int i=1; i<=n; i++) {
scanf("%d",&num[i]);
num[i+n]=num[i];
}
init(2*n);
int l,r;
for(int i=1; i<=m; i++) { //查询[1,l],[r,n]区间有多少个不同的数字转换成查询[r,l+n]区间有多少个不同的数字的问题
scanf("%d %d",&l,&r);
p[i].l=r;
p[i].r=l+n;
p[i].id=i;
}
sort(p+1,p+m+1,cmd); //离线处理
solve(n,m);
sort(p+1,p+m+1,cmd1);
for(int i=1; i<=m; i++) {
printf("%d\n",p[i].ans);
}
//debug(n);
}
return 0;