【BZOJ5319】军训列队(JSOI2018)-主席树+二分
测试地址:军训列队
做法:本题需要用到主席树+二分。
首先可以证明,最好的匹配方法是,按照区间内权值顺序从小到大依次匹配。证明如下:
假设区间内有两个数分别匹配上了和,那么:
当或时,交换彼此的匹配后,距离和不发生变化;
当时,交换彼此的匹配后,距离和减少。
因此发生逆序时交换答案总会变优,于是显然没有逆序的匹配是最优的。
现在要求距离和,因为序列中的值互不相同,那么区间中的数在排序后,是单调递增的,而显然也是递增的,斜率为,显然的增长速度一定比的增长速度快(至少也不慢),所以一旦某个时刻中某数超过了它对应的中的数,后面的时刻中中的数就不可能再小于等于它们对应的中的数。
于是两个序列中对位之差会从负,到,再到正,于是我们需要尝试找到差值正负的分界点,在分界点两侧就可以简单地求和了。
我们使用主席树处理过类似的询问,但是这里有一个问题:一个区间中第大的数是多少,除非我们搜索时走到了其对应的叶子节点,不然我们不可能通过标记计算出这个数值。而的做法显然无法接受,那怎么办呢?
实际上,我们完全没有必要把排名这个信息束缚在已出现在区间内的数上,而是一开始就对所有数定义一个“排名”,表示小于等于该数的数中,有多少个在区间中出现,这个信息显然可以在主席树上求得。显然此时差值也是单调递增的,而且分界点也恰好在原先该分界的地方(因为对于在区间中出现的数,差值还是原值)。那么此时我们要找的分界点就是一个最大的,满足与的差值非负的数,于是我们在主席树上二分即可求出分界点,时间复杂度,这样我们就解决了这一题。
这道题目告诉我,在做题时先不要胡乱规约问题,尽管有时候会起到使目前要解决的问题更清晰的作用,然而如果规约出的问题和原来的问题范围不太一致,死钻规约后的问题可能反而没有结果,要用放缩的角度看待问题。就这样吧。
以下是本人代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m,a[500010],ql,qr;
int rt[500010],tot=0,ch[10000010][2]={0};
ll qk,qsum,qnum;
ll num[10000010]={0},sum[10000010]={0};
ll presum[500010]={0};
struct forsort
{
int id;
ll val;
}f[500010];
bool cmp(forsort a,forsort b)
{
return a.val<b.val;
}
void pushup(int no)
{
num[no]=num[ch[no][0]]+num[ch[no][1]];
sum[no]=sum[ch[no][0]]+sum[ch[no][1]];
}
void insert(int &no,int last,int l,int r,int x)
{
no=++tot;
num[no]=num[last];
sum[no]=sum[last];
ch[no][0]=ch[last][0];
ch[no][1]=ch[last][1];
if (l==r)
{
num[no]++;
sum[no]+=f[l].val;
return;
}
int mid=(l+r)>>1;
if (x<=mid) insert(ch[no][0],ch[last][0],l,mid,x);
else insert(ch[no][1],ch[last][1],mid+1,r,x);
pushup(no);
}
bool query(int no,int last,int l,int r)
{
if (f[r].val<=qk+qnum+num[no]-num[last]-1ll)
{
qsum=qsum+sum[no]-sum[last];
qnum=qnum+num[no]-num[last];
return 1;
}
if (l==r) return 0;
int mid=(l+r)>>1;
bool flag;
flag=query(ch[no][0],ch[last][0],l,mid);
if (flag) query(ch[no][1],ch[last][1],mid+1,r);
return 0;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
f[i].id=i;
scanf("%lld",&f[i].val);
presum[i]=presum[i-1]+f[i].val;
}
sort(f+1,f+n+1,cmp);
for(int i=1;i<=n;i++)
a[f[i].id]=i;
for(int i=1;i<=n;i++)
insert(rt[i],rt[i-1],1,n,a[i]);
for(int i=1;i<=m;i++)
{
qsum=qnum=0;
scanf("%d%d%lld",&ql,&qr,&qk);
query(rt[qr],rt[ql-1],1,n);
ll ans=0;
ans+=qnum*qk+qnum*(qnum-1ll)/2ll-qsum;
ans+=presum[qr]-presum[ql-1]-qsum-(qr-ql+1ll-qnum)*qk;
ans-=(qnum+qr-ql)*(qr-ql-qnum+1ll)/2ll;
printf("%lld\n",ans);
}
return 0;
}