题解-luogu-p4559列队

题目链接

给定数轴上$n$个点,给出$m$条独立的指令,将编号为$[l,r]$ 的点集中到$[k,k+r-l]$的位置且这些点个点位置不能重合,求每次命令中移动的点的距离和的最小值
($n,m≤5×10^5, 1≤a_i,K≤10^6$)

感谢@oy的贡献

本题是主席树的一个简单应用。

阅读题目,不难得出贪心策略。在编号位于$[l,r]$的所有人中,其位置最靠前的跑到$K$位置,位置第二靠前的跑到$K+1$位置,以此类推,可以使体力值总和最小。正确性不给出证明。(不会证)

对于所有学生的位置序列,我们可以建立主席树,并可以用主席树的基本查询操作提取出区间为$[l,r]$的学生位置值域信息。

为了方便学生左右跑动时体力值的统计,我们可以在主席树上额外记录两个数值:$gl$和$gr$,分别表示当前节点对应的区间内所有学生跑动至左端点和右端点所消耗的体力值。在建树过程中即可对这两个变量进行统计,其中$gl$等于当前节点$u$的左儿子上的$gl$,加上$u$的右儿子上的$gl$,再加上右儿子上所有学生从右儿子的左端点跑到$u$的左端点所要消耗的体力值。(可以自己在数轴上模拟一下)

接下来设计查询函数。参数包括值域的左端点和右端点$l$和$r$,以及目标位置区间的左端点和右端点$ql$和$qr$。显然对于以下情况,函数可以直接通过计算得出答案:

  • $r\le ql$ 此时处于当前值域内的所有学生都要往右跑
  • $qr\le l$ 此时处于当前值域内的所有学生都要往左跑
  • 当前值域内没有学生 返回 $0$

否则,必须将值域拆分,递归到左右子节点进行统计。

本题最绕的地方在于学生编号学生位置是两个不同的东西,在设计查询函数时必须区分值域和编号的概念。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// luogu-judger-enable-o2
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

const int MAXN=500005;
const int L=1000005;

int N,M;

struct Node{
int l,r;
ll gl,gr,sum;
}node[L*22+5];
int head[MAXN];
int cnt;

inline void build(Node& u,int l,int r){
u.sum=u.gl=u.gr=0;
if(l==r) return;
int mid=(l+r)>>1;
build(node[u.l=++cnt],l,mid);
build(node[u.r=++cnt],mid+1,r);
}

inline void insert(Node c,Node& u,int l,int r,int p){
u.sum=c.sum+1;u.gl=u.gr=0;
if(l==r) return;
int mid=(l+r)>>1;
if(p<=mid)
insert(node[c.l],node[u.l=++cnt],l,mid,p),u.r=c.r;
else
insert(node[c.r],node[u.r=++cnt],mid+1,r,p),u.l=c.l;
u.gl=node[u.l].gl+node[u.r].gl+node[u.r].sum*(mid-l+1);
u.gr=node[u.r].gr+node[u.l].gr+node[u.l].sum*(r-mid);
}

inline ll query(Node x,Node y,int l,int r,ll ql,ll qr){
ll sum=y.sum-x.sum;
ll gl=y.gl-x.gl,gr=y.gr-x.gr;
if(sum==0) return 0;
if(qr<=l)
return gl+(2*l-ql-qr)*(qr-ql+1)/2;
if(ql>=r)
return gr+(ql+qr-2*r)*(qr-ql+1)/2;
int mid=(l+r)>>1;
ll lsum=node[y.l].sum-node[x.l].sum;
return query(node[x.l],node[y.l],l,mid,ql,ql+lsum-1)+
query(node[x.r],node[y.r],mid+1,r,ql+lsum,qr);
}

int main(){
scanf("%d%d",&N,&M);
build(node[0],1,L);
for(register int i=1;i<=N;++i){
int a;
scanf("%d",&a);
insert(node[head[i-1]],node[head[i]=++cnt],1,L,a);
}
for(register int i=1;i<=M;++i){
ll l,r,k;
scanf("%lld%lld%lld",&l,&r,&k);
printf("%lld\n",query(node[head[l-1]],node[head[r]],1,L,k,k+r-l));
}
return 0;
}

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×