本题解侧重于对贪心原理和时间复杂度的证明。
算法分析
通过某种方法我们不难想到体力消耗最少的情况应该是当前区间的所有学生按照相对位置顺序排到 \([K,K+L-R]\) 。
有一种比较好理解的证明:任意一种顺序都可以通过若干次交换相邻的学生实现,一次交换对于交换的这 \(2\) 个学生各有\(2\)种情况:比原来多走 \(1\) 步;比原来少走 \(1\) 步。下面对于上述贪心分类讨论:
- 交换的 \(2\) 个学生都是从左往右走的。交换之后一个学生多走 \(1\) 步,另一个学生少走 \(1\) 步,与原方案代价相同。
- 交换的 \(2\) 个学生都是从右往左走的。同样交换之后一个学生多走 \(1\) 步,另一个学生少走 \(1\) 步,与原方案代价相同。
- 交换的 \(2\) 个学生一个从左往右走,另一个从右往左走。因为保证是按照相对位置顺序排列,因此一定是靠左边的学生是从左往右走,靠右边的学生是从右往左走,交换之后发现两个学生都比原来多走 \(1\) 步,比原方案代价多。
我们发现在上述贪心下无论怎么交换都不可能让 \(2\) 个学生同时少走 \(1\) 步,因此交换之后不会使答案变更优。
我们把上述贪心的式子写出来,最小代价:
这里把绝对值去掉,分类讨论, \(rk_i+K-1\) 可通过等差数列求和得出, \(a_i\) 通过主席树维护。
下面我们重点来看时间复杂度的证明。
分类讨论结果如下:
- 当前区间无学生,直接返回 \(0\) 。
- 当前区间完全满足 \(a_i-(rk_i+K-1)<0\) ,即当前权值区间中所有学生都应向左跑,此时代价和为 \(\sum rk_i+K-1-a_i\) ,分离可得为 \(\sum rk_i+K-1-\sum a_i\) ,直接返回答案。
- 当前区间完全满足 \(a_i-(rk_i+K-1)>0\) ,即当前权值区间中所有学生都应向右跑,此时代价和为 \(\sum a_i-(rk_i+K-1)\) ,分离可得为 \(\sum a_i-\sum rk_i+K-1\) ,直接返回答案。
- 当前区间不完全满足上述 \(2\) 式,即当前权值区间中一部分学生向左跑,一部分学生向右跑。递归处理。
这里复杂度可以保证单次询问 \(O(\log n)\) 。我们把当前询问学生区间 \(a_l,a_{l+1},\cdots,a_r\) 排序,记为 \(b_1,b_2,\cdots,b_{r-l+1}\) 。显然 \(a_l,a_{l+1},\cdots,a_r\) 和 \(b_1,b_2,\cdots,b_{r-l+1}\) 对于当前询问等价。
因为学生位置互不相同,因此满足 \(b_i\ge b_{i-1}+1\) 。
两边同时 \(-i\) ,即 \(b_i-i\ge b_{i-1}-(i-1)\) 。
不难发现对于 \(b\) 而言 \(rk_i=i\ ,\ rk_{i-1}=i-1\) ,上式即为 \(b_i-rk_i>=b_{i-1}-rk_{i-1}\) 。
对于一组询问, \(K\) 是固定的,因此绝对值里面的式子是单调的,上述递归可以看作是寻找绝对值式子正负的唯一分界点,因此保证单次询问复杂度为 \(\log n\) 。
代码实现
我才不会说我把主席树写成了普通动态开点线段树还调了很久。
#include<bits/stdc++.h>
using namespace std;
#define maxn 1000005
#define maxm 4000005
#define inf 0x3f3f3f3f
#define LL long long
#define mod 1000000007
#define local
void file(string s){freopen((s+".in").c_str(),"r",stdin);freopen((s+".out").c_str(),"w",stdout);}
template <typename Tp> void read(Tp &x){
int fh=1;char c=getchar();x=0;
while(c>'9'||c<'0'){if(c=='-'){fh=-1;}c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c&15);c=getchar();}x*=fh;
}
int n,m;
LL st[maxn<<4];
int T[maxn],lson[maxn<<4],rson[maxn<<4],siz[maxn<<4],tot;
int a[maxn];
#define mid ((l+r)>>1)
void pushup(int x){
st[x]=st[lson[x]]+st[rson[x]];
siz[x]=siz[lson[x]]+siz[rson[x]];
}
int ins(int x,int p,int l,int r,int v){
x=++tot;
st[x]=st[p];siz[x]=siz[p];lson[x]=lson[p];rson[x]=rson[p];
if(l==r){
st[x]+=v;siz[x]++;
return x;
}
if(v<=mid)lson[x]=ins(lson[x],lson[p],l,mid,v);
else rson[x]=ins(rson[x],rson[p],mid+1,r,v);
pushup(x);
return x;
}
LL get_sum(int l,int r){
return 1ll*(l+r)*(r-l+1)/2;
}
LL query(int x,int p,int l,int r,int K,int rk){
if(siz[x]-siz[p]==0)return 0;
if(r-(rk+siz[x]-siz[p])<K-1)return get_sum(K+rk,K+rk+siz[x]-siz[p]-1)-(st[x]-st[p]);
if(l-rk>K-1)return (st[x]-st[p])-get_sum(K+rk,K+rk+siz[x]-siz[p]-1);
return query(lson[x],lson[p],l,mid,K,rk)+query(rson[x],rson[p],mid+1,r,K,rk+siz[lson[x]]-siz[lson[p]]);
}
signed main(){
int l,r,K,mx=0;
read(n);read(m);
for(int i=1;i<=n;i++)read(a[i]),mx=max(mx,a[i]);
for(int i=1;i<=n;i++)T[i]=ins(T[i],T[i-1],1,mx,a[i]);
for(int i=1;i<=m;i++){
read(l);read(r);read(K);
printf("%lld\n",query(T[r],T[l-1],1,mx,K,0));
}
return 0;
}