BZOJ5319: [Jsoi2018]军训列队

BZOJ5319: [Jsoi2018]军训列队

https://lydsy.com/JudgeOnline/problem.php?id=5319

分析:

  • 易知把所有人按原本的顺序放到\([K,K+len-1]\)这些位置上是最优的。
  • 我们只需要求一个\(mid\), 满足从\(mid\)以后的人都是向左移动。
  • 这个可以用二分+主席树在\(O(nlog^2)\)的时间内解决, 然后过不去。
  • 变成直接在主席树上二分就好了,需要一点小技巧。
  • 即二分走的区间不一定包含答案点,但可能是答案点减\(1​\), 在叶子上特判即可。

代码:

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
using namespace std;
typedef long long ll;
#define N 500050
#define M 1500000
int n,m,a[N];
int siz[N*30],ls[N*30],rs[N*30],cnt,root[N];
ll sum[N*30];
void update(int l,int r,int x,int &p,int q) {
	p=++cnt; ls[p]=ls[q]; rs[p]=rs[q]; siz[p]=siz[q]+1; sum[p]=sum[q]+x;
	if(l==r) return ;
	int mid=(l+r)>>1;
	if(x<=mid) update(l,mid,x,ls[p],ls[q]);
	else update(mid+1,r,x,rs[p],rs[q]);
}
ll c,now,s[N];
ll S(ll l,ll r) {return (l+r)*(r-l+1)/2;}
ll query(int l,int r,int p,int q) {
	if(l==r) {
		if(siz[p]>siz[q]&&l<now) {c++; return l;}
		else return 0;
	}
	int mid=(l+r)>>1,sizls=siz[ls[p]]-siz[ls[q]];
	if(!sizls) return query(mid+1,r,rs[p],rs[q]);
	if(mid<=now+sizls-1) {
		c+=sizls, now+=sizls;
		return query(mid+1,r,rs[p],rs[q])+sum[ls[p]]-sum[ls[q]];
	}else return query(l,mid,ls[p],ls[q]);
}
int main() {
	scanf("%d%d",&n,&m);
	int i,l,r,k;
	for(i=1;i<=n;i++) scanf("%d",&a[i]),s[i]=s[i-1]+a[i],update(1,M,a[i],root[i],root[i-1]);
	while(m--) {
		scanf("%d%d%d",&l,&r,&k);
		now=k; c=0;
		ll tot=query(1,M,root[r],root[l-1]);
		
		ll ans=S(k,k+c-1)-tot  +  s[r]-s[l-1]-tot-S(k+c,k+r-l);
		printf("%lld\n",ans);
	}
}
posted @ 2019-01-13 21:09  fcwww  阅读(166)  评论(0编辑  收藏  举报