[JSOI2018]列队
Description:
作为一名大学生,九条可怜在去年参加了她人生中的最后一次军训。
军训中的一个重要项目是练习列队,为了训练学生,教官给每一个学生分配了一个休息位置。每次训练开始前,所有学生都在各自的休息位置休息,但是当教官发出集合命令后,被点到的学生必须要到指定位置集合。
为了简化问题,我们把休息位置和集合位置抽象成一根数轴。一共有 \(n\) 个学生,第 \(i\) 个学生的休息位置是 \(a_i\)。每一次命令,教官会指定一个区间 \([l,r]\) 和集合点 \(K\) ,所有编号在 \([l,r]\) 内的学生都必须赶到集合点列队。在列队时,每一个学生需要选择 \([K,K+r-l]\) 中的一个整数坐标站定且不能有任何两个学生选择的坐标相同。学生从坐标 \(x\) 跑到坐标 \(y\) 需要耗费体力 \(\vert y-x \vert\) 。
在一天的训练中,教官一共发布了 \(m\) 条命令 \((l,r,K)\) ,现在你需要计算对于每一条命令,在所有可能的列队方案中,消耗的体力值总和最小是多少。
以下是对题意的一些补充:
任何两条命令是无关的,即在一条集合命令结束后,所有学生都会回到自己的休息位置,然后教官才会发出下一条命令。
在集合的时候,可能有编号不在 \([l,r]\) 内的学生处在区间 \([K,K+r-l]\) 中,这时他会自己跑开,且跑动的距离不记在消耗的体力值总和中。
Hint:
\(n,m \le 10^5,a_i,k \le 10^6\)
Solution:
都是列队,都是毒瘤题,懂的都懂
好吧其实也不算毒瘤
思路很容易想到,就是集合的相对顺序与休息时的相对顺序一样时体力值和取到最小
关键是怎么统计这个最小值
可以发现所有人可以分成向左走和向右走
向左走的贡献: \(\sum k+rk_i-1-a_i\)
向右走的贡献: \(\sum a_i-k+rk_i-1\)
\(\sum a_i\) 是定值 \(\sum k+rk_i-1\) 是个等差数列,直接算就行
如何判断向左向右呢? 魔改版主席树一直递归左右区间求解,具体看代码
#include <map>
#include <set>
#include <stack>
#include <cmath>
#include <queue>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int bd=1e6+5,mxn=5e5+5,mxm=2e7+5;
int n,m,cnt,a[mxn],hd[mxn];
int rt[mxm],ls[mxm],rs[mxm],sz[mxm];
ll sum[mxm];
inline int read() {
char c=getchar(); int x=0,f=1;
while(c>'9'||c<'0') {if(c=='-') f=-1;c=getchar();}
while(c<='9'&&c>='0') {x=(x<<3)+(x<<1)+(c&15);c=getchar();}
return x*f;
}
inline void chkmax(int &x,int y) {if(x<y) x=y;}
inline void chkmin(int &x,int y) {if(x>y) x=y;}
struct ed {
int to,nxt;
}t[mxn<<1];
inline void add(int u,int v) {
t[++cnt]=(ed) {v,hd[u]}; hd[u]=cnt;
}
void update(int las,int &p,int l,int r,int val)
{
if(!p) p=++cnt; sz[p]=sz[las]+1; sum[p]=sum[las]+val;
if(l==r) return ; int mid=(l+r)>>1;
if(val<=mid) update(ls[las],ls[p],l,mid,val),rs[p]=rs[las];
else update(rs[las],rs[p],mid+1,r,val),ls[p]=ls[las];
}
ll query(int las,int p,int l,int r,int s,int k)
{
if(!p) return 0;
ll num=sz[p]-sz[las],val=sum[p]-sum[las];
if(l>=k+s) return val-(2*k+2*s+num-1)*num/2;
if(r<=k+s+num-1) return (2*k+2*s+num-1)*num/2-val;
int mid=(l+r)>>1; num=sz[ls[p]]-sz[ls[las]];
return query(ls[las],ls[p],l,mid,s,k)+query(rs[las],rs[p],mid+1,r,s+num,k);
}
int main()
{
n=read(); m=read(); int l,r,k;
for(int i=1;i<=n;++i) a[i]=read(),update(rt[i-1],rt[i],1,bd,a[i]);
for(int i=1;i<=m;++i) {
l=read(); r=read(); k=read();
printf("%lld\n",query(rt[l-1],rt[r],1,bd,0,k));
}
return 0;
}