[JSOI2018]列队
这题好妙啊
首先发现这是一道可怜题,又发现这道题是\(t3\),而且还是数据结构,怎么看都不是很可做的样子
但是事实上这就是最可做的那一道题啊
我们先胡乱发现一下,发现肯定存在某一个位置,这个位置左边的人都往右跑,这个位置右边的人都往左跑
这非常显然啊,因为一旦另这个位置左边的一个人跨过这个位置往右边跑,那么就必须要右边的一个人再往左边跑,交换两个人只会使得答案更大
于是我们考虑找到这个位置
设这个位置为\(x\)
\(x\)显然需要满足一个条件,\(x-k+1=num\),\(num\)为\(x\)左边的人数,也就是左边得人刚好够把\([k,x]\)填满,这样左边得人就不要往右边跑了
我们简单移项,发现\(x-num=k-1\)
分析一下\(x-num\),发现这个东西显然是单调不降的,因为\(x\)增加\(1\),\(num\)最多也就增加\(1\)
于是我们我们要找的\(x\)位置肯定满足\(x-num>=k-1\)
这个东西我们显然可以通过在主席树上二分找到
二分的时候我们顺便求出这个位置左右所有有人的位置的坐标和就可以了
代码
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#define re register
#define LL long long
const int maxn=5e5+6;
const int M=22*maxn;
inline int read() {
char c=getchar();int x=0;while(c<'0'||c>'9') c=getchar();
while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-48,c=getchar();return x;
}
int n,m,Q,cnt,K;
int l[M],r[M],d[M];
LL s[M],s1,s2;
int rt[maxn];
int change(int pre,int x,int y,int pos) {
int root=++cnt;
d[root]=d[pre]+1;s[root]=s[pre]+pos;
if(x==y) return root;
l[root]=l[pre],r[root]=r[pre];
int mid=x+y>>1;
if(pos<=mid) l[root]=change(l[pre],x,mid,pos);
else r[root]=change(r[pre],mid+1,y,pos);
return root;
}
int query(int p1,int p2,int x,int y,int k) {
if(x==y) return s1+=s[p2]-s[p1],x;
int now=d[l[p2]]-d[l[p1]];
int mid=x+y>>1;
if(mid-now-k>=K-1) return s2+=s[r[p2]]-s[r[p1]],query(l[p1],l[p2],x,mid,k);
return s1+=s[l[p2]]-s[l[p1]],query(r[p1],r[p2],mid+1,y,k+now);
}
inline LL sum(LL x,LL y) {
return (1ll*(y+1)*y-1ll*(x+1)*x)/2ll;
}
int main() {
n=read();Q=read();m=2e6;
for(re int i=1;i<=n;i++) rt[i]=change(rt[i-1],0,m,read());
int x,y;
while(Q--) {
x=read(),y=read(),K=read();s1=0;s2=0;
int t=query(rt[x-1],rt[y],0,m,0);
printf("%lld\n",sum(K-1,t)-s1+s2-sum(t,K+y-x));
}
return 0;
}