题意:
解法:
考虑每个合法区间对答案的贡献.
那么需要先将询问离线.
枚举右端点r,对所有满足[l,r]中最多的数的个数为k的左端点l,执行c[l]++,
对于在右端点r上的询问ql,qr=r,答案ans=sum{
c[ql,r]}.
sum{
c[ql,r]}是区间和,由于c[]的区间和随着r的向右枚举,是动态变化的,因此用线段树维护.
剩下的难点就是,枚举到r的时候,如何找满足条件的所有l,
当固定r的时候,双指针维护一个最小的下标l,满足[l,r]中最多的数恰好出现k次,
但是[l+1,r]也许也是k次,因此固定r时,满足条件的区间是[l,r]左边的一段区间,如下图:
再开一个指针l2,维护满足[l2,r]中最多的数出现k-1次的最小下标.
那么[l,l2-1]就是对于当前r,满足条件的所有左端点了,
对c[l,l2-1]++就是对线段数进行区间加法.
code:
#include <bits/stdc++.h>
#define int long long
#define PI pair<int,int>
using namespace std;
const int maxm=3e5+5;
vector<PI>g[maxm];
int cnt2[maxm];
int cnt[maxm];
int ans[maxm];
int a[maxm];
int n,q,k;
struct Tree{
int a[maxm<<2];
int laz[maxm<<2];
void pp(int node){
a[node]=a[node*2]+a[node*2+1];
}
void pd(int node,int l,int r){
if(laz[node]){
int mid=(l+r)/2;
a[node*2]+=laz[node]*(mid-l+1);
a[node*2+1]+=laz[node]*(r-mid);
laz[node*2]+=laz[node];
laz[node*2+1]+=laz[node];
laz[node]=0;
}
}
void update(int st,int ed,int val,int l,int r,int node){
if(st<=l&&ed>=r){
a[node]+=val*(r-l+1);
laz[node]+=val;
return ;
}
pd(node,l,r);
int mid=(l+r)/2;
if(st<=mid)update(st,ed,val,l,mid,node*2);
if(ed>mid)update(st,ed,val,mid+1,r,node*2+1);
pp(node);
}
int ask(int st,int ed,int l,int r,int node){
if(st<=l&&ed>=r){
return a[node];
}
pd(node,l,r);
int mid=(l+r)/2;
int ans=0;
if(st<=mid)ans+=ask(st,ed,l,mid,node*2);
if(ed>mid)ans+=ask(st,ed,mid+1,r,node*2+1);
return ans;
}
}T;
signed main(){
scanf("%lld%lld%lld",&n,&q,&k);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
}
for(int i=1;i<=q;i++){
int l,r;scanf("%lld%lld",&l,&r);
g[r].push_back({
l,i});
}
int l=1,l2=1;
for(int i=1;i<=n;i++){
cnt[a[i]]++;
cnt2[a[i]]++;
while(cnt[a[i]]>k){
cnt[a[l]]--;
l++;
}
while(cnt2[a[i]]>=k){
cnt2[a[l2]]--;
l2++;
}
if(l2-1>=1&&cnt[a[l2-1]]==k){
T.update(l,l2-1,1,1,n,1);
}
for(auto p:g[i]){
int l=p.first,id=p.second;
ans[id]=T.ask(l,i,1,n,1);
}
}
for(int i=1;i<=q;i++){
printf("%lld\n",ans[i]);
}
return 0;
}