传送门:牛客
题目描述:
题目较长,此处暂略
输入:
5 3 5
1 2 2 3 1
1 5
1 2
2 2
2 3
3 5
输出:
2
0
0
1
0
总结一下题意,就是求区间 [ l , r ] [l,r] [l,r]出现次数大于1的花的种类数.
考虑使用主席树或者离线树状数组的方法来解决.由于数据加强的原因,导致主席树在本题中是不能完美通过的,洛谷上可以得133分(T了两个点),在牛客上可以过一般数据(MLE+T).因为牛客上的空间甚至只有可怜的256MB,导致我们的主席树在牛客上被卡的死死的.虽然但是,主席树的解法还是可以了解一下的.
首先是主席树解法:
考虑用
l
a
s
t
1
[
i
]
last1[i]
last1[i]来记录每一个位置的花上一次出现的位置,那么对于本题来说,我们需要记录区间内出现次数大于1,这就意味着花上一次出现的位置要在我们的区间
[
l
,
r
]
[l,r]
[l,r]里面就可以了.所以此时需要计算的是就是:
∑
l
r
(
l
a
s
t
1
[
i
]
>
=
l
)
\sum_{l}^r{(last1[i]>=l)}
l∑r(last1[i]>=l)为主席树可以解决的经典题目之一.
当然此时就有人要有疑问了,上面的解法显然是有一个问题的,就是我们区间里的花的种类是可能重复的,也就是当我们区间里有三朵同样的花时,此时我们直接使用上述
l
a
s
t
1
last1
last1会发现会被重复计算一次.那我们要怎么解决这个问题呢,我们可以在第三朵相同的花出现的时候将前一朵花的贡献删除掉.也就是意味着当我们的第三朵花在区间里面时,前面的花的贡献因为重复了所以可以直接删除掉.
因为对于我们的主席树来说,我们最终的答案是有首尾两个相减得到的,所以我们只在末尾出更改只影响到了包括这三朵花的区间,而对于这样的区间来说,此时显然我们只需要第三朵花的贡献即可.对于不完全包括这三朵花的区间,我们发现要么此前的花本身就没有贡献,删掉也无所谓;要么三朵花都没有贡献,所以删掉不影响这些区间,所以这么做就巧妙的解决了这个问题.
在搞清楚上述关键点之后,直接使用主席树来解决即可:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define root 1,n,1
#define ls rt<<1
#define rs rt<<1|1
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
ll x=0,w=1;char ch=getchar();
for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
return x*w;
}
#define maxn 2000100
const double eps=1e-8;
#define int_INF 0x3f3f3f3f
#define ll_INF 0x3f3f3f3f3f3f3f3f
struct PerSegment_tree{
int sum,lnum,rnum;
}tree[maxn<<5];int cnt=0;int RT[maxn];
int build(int l,int r) {
int p=++cnt;tree[p].sum=0;
if(l==r) {
return p;
}
int mid=(l+r)>>1;
tree[p].lnum=build(l,mid);
tree[p].rnum=build(mid+1,r);
return p;
}
int update(int per,int l,int r,int pos,int val) {
if(pos==0) return per;
int p=++cnt;
tree[p]=tree[per];tree[p].sum+=val;
if(l==r) return p;
int mid=(l+r)>>1;
if(pos<=mid) tree[p].lnum=update(tree[per].lnum,l,mid,pos,val);
else tree[p].rnum=update(tree[per].rnum,mid+1,r,pos,val);
return p;
}
int query(int per,int now,int l,int r,int k) {
if(l==r) return tree[now].sum-tree[per].sum;
int mid=(l+r)>>1;
if(k>mid) {
return query(tree[per].rnum,tree[now].rnum,mid+1,r,k);
}
else {
int s=tree[tree[now].rnum].sum-tree[tree[per].rnum].sum;
return s+query(tree[per].lnum,tree[now].lnum,l,mid,k);
}
}
int a[maxn];
int n,c,m;int last1[maxn],last2[maxn];
int main() {
n=read();c=read();m=read();
RT[0]=build(1,c);
for(int i=1;i<=n;i++) {
a[i]=read();
RT[i]=update(RT[i-1],1,c,last1[a[i]],-1);
RT[i]=update(RT[i],1,c,last2[a[i]],1);
last1[a[i]]=last2[a[i]];
last2[a[i]]=i;
}
for(int i=1;i<=m;i++) {
int l=read(),r=read();
printf("%d\n",query(RT[l-1],RT[r],1,c,l));
}
return 0;
}
方法二:离线树状数组
我们可以将所有的询问区间进行一个排序(按区间右端点从小到大排).那么此时我们的关注点就是所有小于当前区间的右端点的花了.
对于所有小于当前区间右端点的花来说,假设此时我们先后出现了两朵颜色相同的花,此时我们需要将第一朵加入我们的贡献.因为只有当我们的两朵都在区间内的时候,我们此时才有贡献,所以算倒数第二朵的.假设我们此时先后出现了三朵颜色相同的花,注意,此时无论我们的左端点时如何,显然我们应该删除第一朵花原本的贡献,因为无论我们的左端点所在的情况如何,越靠近右端点的花显然是越有可能贡献的(这个可以仔细理解一下).
并且因为我们保证了越靠近后面的花的贡献,所以此时我们的询问区间从小到大排的正确性也就是可以保证了.
讲一讲离线做法的精华所在:直接做这道题是不好做的,但是假设此时我们改变一下此题,假设我们此时的问题是计算区间 [ l , n ] [l,n] [l,n]的花的贡献,我们是不是就可以使用上面的方法来做了,但是此时我们一旦使用了上面的方法,那么对于 n n n之前的所有区间就会产生影响.因为我们删除了前面的花的贡献.但是此时我们进行排序了,就保证后面区间都是在当前区间后面的,对于后面的区间来说,假设这区间的左端点在我们当前区间左端点的左边,那么没关系,因为当前区间左端点的贡献已经记录在区间里了.假设这区间的左端点在当前区间左端点右边,那么也没关系
然后使用树状数组解决即可:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define root 1,n,1
#define ls rt<<1
#define rs rt<<1|1
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
ll x=0,w=1;char ch=getchar();
for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
return x*w;
}
#define maxn 2001000
const double eps=1e-8;
#define int_INF 0x3f3f3f3f
#define ll_INF 0x3f3f3f3f3f3f3f3f
inline int lowbit(int x) {
return x&(~x+1);
}
int tree[maxn];int n,m,c;int a[maxn];
void Add(int pos,int val) {
while(pos<=n) {
tree[pos]+=val;
pos+=lowbit(pos);
}
}
int query(int pos) {
int ans=0;
while(pos) {
ans+=tree[pos];
pos-=lowbit(pos);
}
return ans;
}
struct Ask{
int l,r,id;
}ask[maxn];
bool cmp(Ask aa,Ask bb) {
return aa.r<bb.r;
}
int last1[maxn];int last2[maxn];int ans[maxn];
int main() {
n=read();c=read();m=read();
for(int i=1;i<=n;i++) {
a[i]=read();
}
for(int i=1;i<=m;i++) {
ask[i].l=read();ask[i].r=read();ask[i].id=i;
}
sort(ask+1,ask+m+1,cmp);
int pos=1;
for(int i=1;i<=m;i++) {
while(pos<=n&&pos<=ask[i].r) {
if(last1[a[pos]]==0) {
last1[a[pos]]=pos;
}
else {
if(last2[a[pos]]==0) {
Add(last1[a[pos]],1);
last2[a[pos]]=pos;
}
else {
Add(last1[a[pos]],-1);
Add(last2[a[pos]],1);
last1[a[pos]]=last2[a[pos]];
last2[a[pos]]=pos;
}
}
pos++;
}
ans[ask[i].id]=query(ask[i].r)-query(ask[i].l-1);
}
for(int i=1;i<=m;i++) {
printf("%d\n",ans[i]);
}
return 0;
}