HihoCoder 1488 : 排队接水(莫队+树状数组)

Posted ---学习ing---

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了HihoCoder 1488 : 排队接水(莫队+树状数组)相关的知识,希望对你有一定的参考价值。

描述

有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

 

思路:贪心可知,时间小的在前。但是排序是不可能的,需要更高效的方法,注意到ai<=2e5,适合用树状数组记录a[i]的个数前缀和,以及a[i]的前缀和。

可以离线,所以用莫队+树状数组,莫队的话,第一次写这中数学类型的转移,开始还有点抵触,但是拿出笔一划,公式也不难。

 

对于暴力的公式,即L<=i<=R的所有i的前缀和:

for(i=L;i<=R;i++)
 for(j=L;j<=i;j++)
  sum+=a[j];

那么现在加一个第i=x进去,则对新的 i=x,需要多累加前缀:

for(j=L;j<=x;j++)
  sum+=a[j]; 

对于后面的i>=x,都需要累加一个j=x,即a[x]*后面的个数。 

for(i=x;i<=R;i++)
 for(j=L;j<=i;j++)
  sum+=a[j]; 

然后把上面的转化为树状数组的前缀和即可。  两个树状数组,分别记录个数和累加和。

 

#include<cmath>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
using namespace std;
#define ll long long
const int maxn=20000;
ll a[maxn+10],num[maxn+10],cnt,B,l,r,tmp;
ll sum[maxn+10];
struct in{ ll L;ll R;ll id; ll ans;}s[maxn+10];
bool cmp(in x,in y){ if(x.L/B==y.L/B) return x.R/B<y.R/B; return x.L/B<y.L/B; }
bool cmp2(in x,in y){ return x.id<y.id;}
void update(){ cnt=0; memset(num,0,sizeof(num));memset(sum,0,sizeof(sum)); }
void addnum(ll x,ll y) { while(x<=maxn){ num[x]+=y; x+=(-x)&x; } }
void addsum(ll x,ll y) { while(x<=maxn){ sum[x]+=y; x+=(-x)&x; } }
int querynum(int x){ ll res=0; while(x>0){ res+=num[x]; x-=(-x)&x;} return res; }
int querysum(int x){ ll res=0; while(x>0){ res+=sum[x]; x-=(-x)&x;} return res;}
int main()
{
    
    
    ll T,n,m,i;ll tsum;
    scanf("%d",&T);
    while(T--){
        update();
        scanf("%lld%lld",&n,&m);
        for(i=1;i<=n;i++) scanf("%lld",&a[i]);
        for(i=1;i<=m;i++) s[i].id=i,scanf("%lld%lld",&s[i].L,&s[i].R);
        B=sqrt(n);
        sort(s+1,s+m+1,cmp);
        l=r=1; tmp=a[1]; addnum(a[1],1); addsum(a[1],a[1]);
        for(i=1;i<=m;i++){
            while(l<s[i].L){
                tsum=querysum(a[l]);
                tmp-=tsum;
                tmp-=(querynum(maxn)-querynum(a[l]))*a[l];
                addnum(a[l],-1);
                addsum(a[l],-a[l]);    
                l++;
            }
            while(l>s[i].L){
                l--;
                addnum(a[l],1);
                addsum(a[l],a[l]);            
                tsum=querysum(a[l]);
                tmp+=tsum;
                tmp+=(querynum(maxn)-querynum(a[l]))*a[l];
            }
            while(r<s[i].R){
                r++;
                addnum(a[r],1);
                addsum(a[r],a[r]);
                tsum=querysum(a[r]);
                tmp+=tsum;
                tmp+=(querynum(maxn)-querynum(a[r]))*a[r];
            }
            while(r>s[i].R){
                tsum=querysum(a[r]);
                tmp-=tsum;
                tmp-=(querynum(maxn)-querynum(a[r]))*a[r];
                addnum(a[r],-1);
                addsum(a[r],-a[r]);    
                r--;
            }
            s[i].ans=tmp;
        }
        sort(s+1,s+m+1,cmp2);
        for(i=1;i<=m;i++) printf("%lld\n",s[i].ans);
    }
    return 0;
}

 

以上是关于HihoCoder 1488 : 排队接水(莫队+树状数组)的主要内容,如果未能解决你的问题,请参考以下文章

问题 A: 贪心排队接水

洛谷 P1223 排队接水

洛谷——P1223 排队接水

排队接水

洛谷 P1223 排队接水

1223排队接水