[JSOI2018]列队

[JSOI2018]列队_第1张图片

  • 性质1:把若干个人塞到一个连续区间内,那么从左向右依次对应一定是最优的。
  • 性质2:一定存在一个分界点,左边的人都是在区间点的左边,右边的人都是在区间点的右边。
  • 因此,很显然有一个 n l o g 2 n nlog^2n nlog2n的做法,二分分界点,主席树查询第 k k k大判断。
  • 但是,其实我们的二分是多余的,直接在主席树上二分就好了,边二分边计算答案。
#include
#define ll long long
using namespace std;
const int N=5e5+10;
const int inf=1e6;
int n,m,tot,l,r,K,sum[N*21];ll num[N*21];int ls[N*21],rs[N*21],rt[N],a[N];
ll cal(int k,int cnt){return 1LL*(k+k+cnt-1)*cnt/2;}
char buf[1<<15],*fs,*ft;
char getc(){
	return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
}
int read(){
	char ch=getc();int num=0,f=1;
	while(!isdigit(ch)){ch=getc();}
	while(isdigit(ch)){num=(num<<1)+(num<<3)+(ch^48);ch=getc();}
	return num*f;
}
void update(int &o,int pre,int l,int r,int x){
	o=++tot;
	sum[o]=sum[pre]+1;
	num[o]=num[pre]+x;
	ls[o]=ls[pre];
	rs[o]=rs[pre];
	if(l==r) return ;
	int mid=l+r>>1;
	if(x<=mid) update(ls[o],ls[pre],l,mid,x);
	else update(rs[o],rs[pre],mid+1,r,x);
}
ll query(int s,int t,int l,int r,int k){
	ll SUM=num[t]-num[s];
	int cnt=sum[t]-sum[s];
	if(!cnt) return 0;
	if(l>=k) return SUM-cal(k,cnt);
	if(r<=k) return cal(k,cnt)-SUM;
	cnt=sum[ls[t]]-sum[ls[s]];
	int mid=l+r>>1;
	return query(ls[s],ls[t],l,mid,k)+query(rs[s],rs[t],mid+1,r,k+cnt);
}
ll print(ll x){if(x>9)print(x/10);putchar(x%10+'0');}
int main(){
	n=read(),m=read();
	for(register int i=1;i<=n;++i) a[i]=read(),update(rt[i],rt[i-1],1,inf,a[i]);
	while(m--){
		ll ans=0;
		l=read(),r=read(),K=read();
		ans=query(rt[l-1],rt[r],1,inf,K);
		print(ans);puts("");
	}
	return 0;
}

你可能感兴趣的:(主席树)