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
",ans);
}
}