一、题目
二、解法
因为最优解的路径两两不交,所以一定是有一些学生向左跑,一些学生向右跑。
首先有一个显然的思路,可以先二分答案,我们要使得刚好左边的人能填满左边的位置,那么答案就是左边的人跑到左边(假设
t
t
t人),右边的人跑到右边(下文的
a
a
a要用主席树维护):
∑
i
=
1
t
k
+
i
−
1
−
∑
a
l
e
f
t
+
∑
a
r
i
g
h
t
−
∑
i
=
t
+
1
k
k
+
i
−
1
\sum_{i=1}^t k+i-1-\sum a_{left}+\sum a_{right}-\sum_{i=t+1}^k k+i-1
i=1∑tk+i−1−∑aleft+∑aright−i=t+1∑kk+i−1这样做的时间复杂度是
O
(
n
log
2
n
)
O(n\log ^2n)
O(nlog2n),受到[省选联考 2020 A/B 卷] 冰火战士的启发,我们可以考虑主席树上二分做到
O
(
n
log
n
)
O(n\log n)
O(nlogn)(用主席树是因为保留
[
l
,
r
]
[l,r]
[l,r]的编号需要用它来差分),我们考虑一个位置区间
[
l
,
r
]
[l,r]
[l,r],也就是当一个区间全部往右跑或者往左跑的时候返回,否者继续递归,因为分界点只有一个,所以递归下去的两部分有一部分会直接结束。
那么怎么判断一个区间里的人是否能全部走左或者走右呢?假设前面已经填了 f f f个位置,那么如果 k + f ≤ l k+f\leq l k+f≤l的话说明所有人都要往左跑,如果 k + f + s z − 1 ≥ r k+f+sz-1\geq r k+f+sz−1≥r就说明所有人要往右跑( s z sz sz是人的个数)
代码出奇的短。
#include <cstdio>
const int M = 500005;
const int N = 25*M;
const int up = 1000000;
#define ll long long
int read()
{
int x=0,flag=1;char c;
while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x*flag;
}
int n,m,cnt,rt[M],ls[N],rs[N],siz[N];ll sum[N];
int upd(int y,int l,int r,int id)
{
int x=++cnt;
ls[x]=ls[y];rs[x]=rs[y];
siz[x]=siz[y];sum[x]=sum[y];
siz[x]++;sum[x]+=id;
if(l==r) return x;
int mid=(l+r)>>1;
if(mid>=id) ls[x]=upd(ls[x],l,mid,id);
else rs[x]=upd(rs[x],mid+1,r,id);
return x;
}
ll ask(int x,int y,int l,int r,int f,int k)
{
if(!(siz[x]-siz[y])) return 0;
ll sz=siz[x]-siz[y],sm=sum[x]-sum[y];
if(l>=k+f) return sm-(2*f+2*k+sz-1)*sz/2;
if(r<=k+f+sz-1) return (2*f+2*k+sz-1)*sz/2-sm;
int mid=(l+r)>>1,t=siz[ls[x]]-siz[ls[y]];
return ask(ls[x],ls[y],l,mid,f,k)+ask(rs[x],rs[y],mid+1,r,f+t,k);
}
signed main()
{
n=read();m=read();
for(int i=1;i<=n;i++)
{
int x=read();
rt[i]=upd(rt[i-1],1,up,x);
}
while(m--)
{
int l=read(),r=read(),k=read();
printf("%lld\n",ask(rt[r],rt[l-1],1,up,0,k));
}
}