[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;
}

posted @ 2019-03-18 22:39  cloud_9  阅读(185)  评论(0编辑  收藏  举报