题目描述
作为一名大学生,九条可怜在去年参加了她人生中的最后一次军训。
军训中的一个重要项目是练习列队,为了训练学生,教官给每一个学生分配了一个休息位置。每次训练开始前,所有学生都在各自的休息位置休息,但是当教官发出集合命令后,被点到的学生必须要到指定位置集合。
为了简化问题,我们把休息位置和集合位置抽象成一根数轴。一共有 n 个学生,第 i 个学生的休息位置是 a_i 。每一次命令,教官会指定一个区间 [l,r] 和集合点 K ,所有编号在 [l,r] 内的学生都必须赶到集合点列队。在列队时,每一个学生需要选择 [K,K+r-l] 中的一个整数坐标站定且不能有任何两个学生选择的坐标相同。学生从坐标 x 跑到坐标 y 需要耗费体力 ∣y−x∣ 。
在一天的训练中,教官一共发布了 m 条命令 (l,r,K) ,现在你需要计算对于每一条命令,在所有可能的列队方案中,消耗的体力值总和最小是多少。
以下是对题意的一些补充:
任何两条命令是无关的,即在一条集合命令结束后,所有学生都会回到自己的休息位置,然后教官才会发出下一条命令。
在集合的时候,可能有编号不在 [l,r] 内的学生处在区间 [K,K+r−l] 中,这时他会自己跑开,且跑动的距离不记在消耗的体力值总和中。
对于 100% 的数据,
n
,
m
≤
5
×
1
0
5
,
1
≤
a
i
,
K
≤
1
0
6
n,m \leq 5 \times 10^5,1 \leq a_i,K \leq 10^6
n,m≤5×105,1≤ai,K≤106
对于 100% 的数据,学生休息的位置两两不同
输入格式
第一行输入两个整数 n,m。
第二行 n 个整数 a_i 表示学生的休息位置。保证学生休息的位置两两不同。
接下来 m 行每行三个整数 l,r,K 表示一条命令。
输出格式
对于每一条命令输出一行一个整数表示最小的体力值总和。
题目分析
首先很容易想到学生集合后相对位置不变一定是最优解之一
比如集合前A、B两人分别在位置k-1和k,集合后A在k、B在k+1和A在k+1、B不动消耗的体力值是一样的
那么问题转化为找到一个位置pos,使得位置1到pos的人数恰好等于pos-k+1
即在位置1到pos的人按相对顺序排在k到pos,而位置pos+1后的人按相对顺序排在pos+1到k+r-l
我们只需要用主席树维护区间内学生的位置即可
位置pos可以在主席树内二分
消耗的体力就是个等差数列求和
注意这题不能离散化
#include<iostream>
#include<vector>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;
#define lowbit(x) ((x)&(-x))
typedef long long lt;
int read()
{
int f=1,x=0;
char ss=getchar();
while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
return f*x;
}
const int maxn=500010;
int n,m;
int a[maxn];
int ch[maxn*24][2],rt[maxn],sz;
lt size[maxn*24],sum[maxn*24];
struct node{lt num,sum;};
int mx=2000010;
node operator + (node a, node b){
return (node){a.num+b.num, a.sum+b.sum};
}
int update(int pre,int ll,int rr,lt x)
{
int tt=++sz;
size[tt]=size[pre]+1; sum[tt]=sum[pre]+x;
ch[tt][0]=ch[pre][0]; ch[tt][1]=ch[pre][1];
int mid=ll+rr>>1;
if(ll<rr)
{
if(x<=mid) ch[tt][0]=update(ch[pre][0],ll,mid,x);
else ch[tt][1]=update(ch[pre][1],mid+1,rr,x);
}
return tt;
}
node query(int Rrt,int Lrt,int ll,int rr,int s,int t)
{
if(ll<=s&&t<=rr) return (node){size[Rrt]-size[Lrt], sum[Rrt]-sum[Lrt]};
int mid=s+t>>1;
node res=(node){0,0};
if(ll<=mid) res=res+query(ch[Rrt][0],ch[Lrt][0],ll,rr,s,mid);
if(rr>mid) res=res+query(ch[Rrt][1],ch[Lrt][1],ll,rr,mid+1,t);
return res;
}
int solve(int Rrt,int Lrt,int ll,int rr,int s,int t,int lnum)
{
if(s>t) return 0;
int mid=s+t>>1, num=size[ch[Rrt][0]]-size[ch[Lrt][0]];
if(num+lnum > mid-ll+1) return solve(ch[Rrt][1],ch[Lrt][1],ll,rr,mid+1,t,lnum+num);
else if(num+lnum < mid-ll+1) return solve(ch[Rrt][0],ch[Lrt][0],ll,rr,s,mid,lnum);
else return mid;
}
lt calc(int Rrt,int Lrt,int pos)
{
lt res=0, num=0, sum=0;
node t;
if(pos!=0)
{
t = query(Rrt,Lrt,1,pos>mx?mx:pos,1,mx); // 1~pos内的人右移的体力消耗
num=t.num; sum=t.sum;
res+=pos*num-sum-(num)*(num-1)/2;
}
t = query(Rrt,Lrt,pos+1>mx?mx:pos+1,mx,1,mx); // // pos+1后的人左移的体力消耗
num=t.num; sum=t.sum;
res+=sum-(pos+1)*num-(num)*(num-1)/2;
return res;
}
int main()
{
n=read(); m=read();
for(int i=1;i<=n;++i)
a[i]=read();
for(int i=1;i<=n;++i)
rt[i]=update(rt[i-1],1,mx,a[i]);
for(int i=1;i<=m;++i)
{
int L=read(),R=read(),k=read(),pos;
if(k==1) pos=0; // 所有人都左移
else pos=solve(rt[R],rt[L-1],k,k+R-L,1,mx,0);
lt ans=calc(rt[R],rt[L-1],pos);
printf("%lld\n",ans);
}
return 0;
}