(看到这题目让我想起了采花大盗。。。)
(好的,正式开始)
原题链接:
洛谷
bzoj
题意简述
求区间出现次数 > = 2 >=2 >=2的。
数据
输入
n c m
//数列长度,每个数的规模,询问个数(n,m,c<=2e6)
l r
l r
...
l r
//m个,1<=l<=r<=n,问l,r中有多少出现次数>=2的
输出
ans
ans
...
ans
//m行,对于每个询问,输出答案
样例
输入
5 3 5
1 2 2 3 1
1 5
1 2
2 2
2 3
3 5
输出
2
0
0
1
0
思路
理论上这个题是主席树做的。。。
但是我主席树一直咕咕咕,出来会打板子,别的不会。
所以我就点开这里 不不不是这里看到了一个神奇的做法如下:
搞一个
n
e
x
t
next
next数组,
n
e
x
t
[
i
]
next[i]
next[i]表示最小的
j
j
j满足
a
[
j
]
=
=
a
[
i
]
a[j]==a[i]
a[j]==a[i],也就是下一个相等的位置。这个
n
e
x
t
next
next数组是我们处理这一类出现次数问题的常用方法。然后,我们搞一个
n
e
x
t
2
[
i
]
=
n
e
x
t
[
n
e
x
t
[
i
]
]
next2[i]=next[next[i]]
next2[i]=next[next[i]]。因为我们要求出现次数
>
=
2
>=2
>=2的,所以这个玩意珂能会有用。
(以上两个数组,如果没有满足条件的数,就等于
0
0
0)
如果我们要查全局,那么我们只要找多少个
i
i
i满足
a
[
i
]
a[i]
a[i]是第二次出现的即珂。然后我们把每个位置上的这个表达式的值算出来,设一个
01
01
01数组(
b
o
o
l
bool
bool数组)为
T
T
T。即:
T
[
i
]
=
{
1
a
[
i
]
是
第
二
次
出
现
0
其
它
情
况
T[i]= \begin{cases} 1 & & & & & & & & & & a[i]是第二次出现\\ 0 & & & & & & & & & & 其它情况 \end{cases}
T[i]={10a[i]是第二次出现其它情况
那么
T
T
T就相当于一个位置对答案的贡献值了。然后我们用树状数组维护这个
T
T
T。要求答案的话,区间求一下和即珂。
那么,如何进行修改工作呢?我们发现,如果只考虑局部,得到的
T
T
T数组和全局中得到的
T
T
T数组珂能是不一样的。因为有些数会出现同时出现在区间外面和里面,就会导致
n
e
x
t
next
next数组值不同。现在的问题就是:我们如何通过全局的
T
T
T值求得局部的
T
T
T值,进而用树状数组的区间和求出答案。
这样我们已经很显然就能想到离线了。那么,如何排序?
画张图:
(绿色的区间是询问)
我们有一个
f
a
n
t
a
s
y
♂
fantasy♂
fantasy♂,对于某一种颜色,如果当前的区间错过了这个点,那么以后就别想再要回来了。如果真就这样,我们枚举到一个点要滚蛋的时候,设这个是
i
i
i,只要在
n
e
x
t
[
i
]
next[i]
next[i]位置减一(因为这样的话,
n
e
x
t
[
i
]
next[i]
next[i]在接下来的区间中,如果出现,只会是第一个位置,观察上图中的
i
i
i在第
2
,
3
2,3
2,3个询问时状态的变化)。同时,在
n
e
x
t
2
[
i
]
next2[i]
next2[i]的位置上还要加一。因为此时
n
e
x
t
2
[
i
]
next2[i]
next2[i]那边就珂能是第
2
2
2个位置,就有珂能让答案加一。
Q:如果 n e x t 2 [ i ] next2[i] next2[i]并不是第 2 2 2个位置,而是第 1 1 1个位置,就不会对答案产生贡献了,但是那个加一还在鸭。。。怎么肥四。。。
A:确实, n e x t 2 [ i ] next2[i] next2[i]也珂能是第 1 1 1个位置。但是如果真的这样的话, n e x t [ i ] next[i] next[i]肯定也滚蛋了一次。在 n e x t [ i ] next[i] next[i]滚蛋的时候,就会把 n e x t 2 [ i ] next2[i] next2[i]上面的加一操作给减掉。
此时,我们知道,为了 f u l f i l l ♂ fulfill♂ fulfill♂这个 f a n t a s y ♂ fantasy♂ fantasy♂,我们要把区间按 l l l排序(也就是按左端点排序),然后离线求得答案,按原序输出。
整理一下思路:
- 求出最开始的贡献数组 T T T
- 离线,将询问按 l l l排序,处理出滚蛋的位置,区间求和计算答案
- 按读入顺序排序,顺序输出答案
代码:
// luogu-judger-enable-o2
#include<bits/stdc++.h>
using namespace std;
namespace Flandle_Scarlet
{
#define N 2001000
class BIT//树状数组,BIT是binary indexed tree的简写
{
public:
int tree[N];
int len;
void BuildTree(int n)
{
len=n;
memset(tree,0,sizeof(tree));
}
void Add(int pos,int x)
{
while(pos<=len)
{
tree[pos]+=x;
pos+=(pos&(-pos));
}
}
int Query(int pos)
{
int ans=0;
while(pos>0)
{
ans+=tree[pos];
pos-=(pos&(-pos));
}
return ans;
}
int RQuery(int l,int r)
{
return Query(r)-Query(l-1);
}
}T;
struct Q1//一个询问
{
int l,r;
int id,ans;//记得保存id,到最后要顺序输出
}Q[N];
bool cmp_l(Q1 a,Q1 b){return a.l<b.l;}
bool cmp_id(Q1 a,Q1 b){return a.id<b.id;}
int n,c,m;
int a[N];
void R1(int &x)
{
x=0;char c=getchar();int f=1;
while(c<'0' or c>'9') f=(c=='-')?-1:1,c=getchar();
while(c>='0' and c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
x=(f==1)?x:-x;
}
void Input()
{
R1(n),R1(c),R1(m);
for(int i=1;i<=n;++i)
{
R1(a[i]);
}
}
int nxt[N],nxt2[N];
int tmp[N];
void Soviet()
{
for(int i=n;i>=1;--i)
{
int c=a[i];
nxt[i]=tmp[c];
tmp[c]=i;
}
for(int i=1;i<=n;++i)
{
nxt2[i]=nxt[nxt[i]];
}//求出nxt和nxt2
memset(tmp,0,sizeof(tmp));//此时把tmp设置为0,改变定义为:tmp[i]是第i中颜色出现的次数
T.BuildTree(n);
for(int i=1;i<=n;++i)
{
int c=a[i];
if (++tmp[c]==2)//第二次出现
{
T.Add(i,1);//记录贡献
}
}
for(int i=1;i<=m;++i)
{
int l,r;
R1(l),R1(r);
Q[i]=(Q1){l,r,i,-1};//此时没有答案,设置ans为-1
}
sort(Q+1,Q+m+1,cmp_l);//按l排序
int pos=1;//上一个区间的l
for(int i=1;i<=m;++i)
{
for(;pos<Q[i].l;++pos)//这些是要滚蛋的点
{
if (nxt[pos]) T.Add(nxt[pos],-1);
if (nxt2[pos]) T.Add(nxt2[pos],1);
}
Q[i].ans=T.RQuery(Q[i].l,Q[i].r);//区间求和
}
sort(Q+1,Q+m+1,cmp_id);//按读入顺序排序,输出答案
for(int i=1;i<=m;++i)
{
printf("%d\n",Q[i].ans);
}
}
void IsMyWife()
{
if (0)
{
freopen("","r",stdin);
freopen("","w",stdout);
}
Input();
Soviet();
}
#undef int //long long
};
int main()
{
Flandle_Scarlet::IsMyWife();
return 0;
}