hihocoder 1488 排队接水

题目

时间限制:10000ms
单点时限:1000ms
内存限制:256MB

描述

有n个小朋友需要接水,其中第i个小朋友接水需要ai分钟。

由于水龙头有限,小Hi需要知道如果为第l个到第r个小朋友分配一个水龙头,如何安排他们的接水顺序才能使得他们等待加接水的时间总和最小。

小Hi总共会有m次询问,你能帮助他解决这个问题吗?

假设3个小朋友接水的时间分别是2,3,4。如果他们依次接水,第一位小朋友等待加接水的时间是2,第二位小朋友是5,第三位小朋友是9。时间总和是16。

输入

第一行一个数T(T<=10),表示数据组数
对于每一组数据:
第一行两个数n,m(1<=n,m<=20,000)
第二行n个数a1…an,表示每个小朋友接水所需时间(ai<=20,000)
接下来m行,每行两个数l和r

输出

对于每次询问,输出一行一个整数,表示答案。

样例输入

1
4 2
1 2 3 4
1 2
2 4

样例输出

4
16

分析

  • 离线算法——莫队算法 O(n1.5logn);
  • 已知查询(l,r),用平衡树维护区间数据,可以O(logn)求解(l-1,r)、(l+1,r)、(l,r-1)、(l,r+1);
  • sqrt(n)分块,左端点在同一块中的查询为一组,组内按右端点排序,调节左右端点即可;
  • 平衡树难写,每个排队时间不超过2e4,可以映射到数组,转换成数组区间求和,这里用树状数组;

代码

#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
typedef uint8_t byte;
typedef int64_t illong;
typedef uint64_t ullong;
typedef uint32_t uint;
#define CLEAN(x) memset(x,0,sizeof(x))
#define TR(i,obj) for(__typeof(obj.begin()) i=obj.begin();i!=obj.end();++i)

struct Node{
    int l,r,ind;
    void set(int l,int r,int ind){
        this->l=l;
        this->r=r;
        this->ind=ind;
    }
    bool operator<(const Node &other)const;
};

const int tsize=1<<15;
int t,n,m,bsize;
int dat[tsize];
Node query[tsize];
illong cur,cl,cr;
illong dst[tsize];

bool Node::operator <(const Node &other)const{
    int sb=l/bsize;
    int ob=other.l/bsize;
    if(sb!=ob) return sb<ob;
    else return r<other.r;
}

struct Arr{
    illong arr[tsize+1];
    void add(int ind,illong value){
        while(ind<=tsize){
            arr[ind]+=value;
            ind+=ind&-ind;
        }
    }
    illong sum(int ind){
        illong rs=0;
        while(ind){
            rs+=arr[ind];
            ind-=ind&-ind;
        }
        return rs;
    }
    void reset(){
        memset(arr,0,sizeof(arr));
    }
};

Arr num,sum;
void rm(int ind){
    illong value=dat[ind];
    cur-=sum.sum(value);
    cur-=value*(num.sum(tsize)-num.sum(value));
    sum.add(value,-value);
    num.add(value,-1);
}
void add(int ind){
    illong value=dat[ind];
    cur+=sum.sum(value)+value;
    cur+=value*(num.sum(tsize)-num.sum(value));
    sum.add(value,value);
    num.add(value,1);
}

int main() {
    scanf("%d",&t);
    while(t--){
        scanf("%d%d",&n,&m);
        for(int i=0;i<n;++i){
            scanf("%d",dat+i);
        }
        cl=0;
        cr=0;
        sum.reset();
        num.reset();
        sum.add(dat[0],dat[0]);
        num.add(dat[0],1);
        cur=dat[0];
        bsize=sqrt(n);
        for(int i=0;i<m;++i){
            int l,r;
            scanf("%d%d",&l,&r);
            --l;--r;
            query[i].set(l,r,i);
        }
        sort(query,query+m);
        for(int i=0;i<m;++i){
            const Node &it=query[i];
            while(cl<it.l)
                rm(cl++);
            while(cl>it.l)
                add(--cl);
            while(cr<it.r)
                add(++cr);
            while(cr>it.r)
                rm(cr--);
            dst[it.ind]=cur;
        }
        for(int i=0;i<m;++i){
            printf("%lld\n",dst[i]);
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值