洛谷P4559 [JSOI2018]列队【主席树】

题目描述

作为一名大学生,九条可怜在去年参加了她人生中的最后一次军训。

军训中的一个重要项目是练习列队,为了训练学生,教官给每一个学生分配了一个休息位置。每次训练开始前,所有学生都在各自的休息位置休息,但是当教官发出集合命令后,被点到的学生必须要到指定位置集合。

为了简化问题,我们把休息位置和集合位置抽象成一根数轴。一共有 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,m5×105,1ai,K106
对于 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值