【BZOJ5319】军训列队(JSOI2018)-主席树+二分

测试地址:军训列队
做法:本题需要用到主席树+二分。
首先可以证明,最好的匹配方法是,按照区间内权值顺序从小到大依次匹配[K,K+rl]。证明如下:
假设区间内有两个数x<y分别匹配上了i+1i,那么:
x<yii+1x<y时,交换彼此的匹配后,距离和不发生变化;
xi<i+1y时,交换彼此的匹配后,距离和减少2
因此发生逆序时交换答案总会变优,于是显然没有逆序的匹配是最优的。
现在要求距离和,因为a序列中的值互不相同,那么区间中的数在排序后,是单调递增的,而[K,K+rl]显然也是递增的,斜率为1,显然a的增长速度一定比[K,K+rl]的增长速度快(至少也不慢),所以一旦某个时刻a中某数超过了它对应的[K,K+rl]中的数,后面的时刻中a中的数就不可能再小于等于它们对应的[K,K+rl]中的数。
于是两个序列中对位之差会从负,到0,再到正,于是我们需要尝试找到差值正负的分界点,在分界点两侧就可以简单地求和了。
我们使用主席树处理过类似的询问,但是这里有一个问题:一个区间中第k大的数是多少,除非我们搜索时走到了其对应的叶子节点,不然我们不可能通过标记计算出这个数值。而O(nlog2n)的做法显然无法接受,那怎么办呢?
实际上,我们完全没有必要把排名这个信息束缚在已出现在区间内的数上,而是一开始就对所有数定义一个“排名”num,表示小于等于该数的数中,有多少个在区间中出现,这个信息显然可以在主席树上求得。显然此时差值也是单调递增的,而且分界点也恰好在原先该分界的地方(因为对于在区间中出现的数,差值还是原值)。那么此时我们要找的分界点就是一个最大的,满足与K+num1的差值非负的数,于是我们在主席树上二分即可求出分界点,时间复杂度O(nlogn),这样我们就解决了这一题。
这道题目告诉我,在做题时先不要胡乱规约问题,尽管有时候会起到使目前要解决的问题更清晰的作用,然而如果规约出的问题和原来的问题范围不太一致,死钻规约后的问题可能反而没有结果,要用放缩的角度看待问题。就这样吧。
以下是本人代码:

#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;
}
posted @ 2018-08-16 21:24  Maxwei_wzj  阅读(93)  评论(0编辑  收藏  举报