hihocode 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


题目链接:http://hihocoder.com/problemset/problem/1488


思路:多区间的问题莫队算法是很好的解决方式,通过排序,使得区间增加减少的最少,然后只需要求出[l,r]转移到[l+1,r],[l-1,r],[l,r-1],[l,r+1],四个转移方程式即可在nsqrt(n)的时间复杂度求出所有的查询区间。

   此题中新加入一个数,我们只需要求出小于等于此数的和和大于此数的个数即可算出这个数加进去之后对答案的贡献度。


代码:

#include<bits/stdc++.h>

using namespace std;

typedef long long ll;
const int MAXN=100000+5;

int a[MAXN];
int cnt[1<<20];
ll result;
ll ans[MAXN],bit1[MAXN],bit2[MAXN];
int unit,n,m,k;

struct Query
{
    int L,R,id;
} query[MAXN];

bool cmp( Query a,Query b )
{
    if( a.L/unit==b.L/unit )return a.R<b.R;
    return a.L/unit<b.L/unit;
}
int lowbit(int x)
{
    return x&(-x);
}
int sum(int x)
{
    int res=0;
    while(x)
    {
        res+=bit1[x];
        x-=lowbit(x);
    }
    return res;
}
int add(int x,int value)
{
    while(x<MAXN)
    {
        bit1[x]+=value;
        x+=lowbit(x);
    }
}
int sum2(int x)
{
    int res=0;
    while(x)
    {
        res+=bit2[x];
        x-=lowbit(x);
    }
    return res;
}
int add2(int x,int value)
{
    while(x<MAXN)
    {
        bit2[x]+=value;
        x+=lowbit(x);
    }
}

void work()
{
    result=0;
    int L=1,R=0;
    for(int i=0; i<m; i++)
    {
        //cout<<query[i].L<<" "<<query[i].R<<endl;
        while( R<query[i].R )
        {
            R++;
            int x=sum(a[R]);
            int w=sum2(a[R]);
            result+=w+(R-L+1-x)*a[R];
            add(a[R],1);
            add2(a[R],a[R]);

        }
        while( R>query[i].R )
        {
            int x=sum(a[R]);
            int w=sum2(a[R]);
            result-=w+(R-L+1-x)*a[R];
            add(a[R],-1);
            add2(a[R],-a[R]);
            R--;
        }
        while( L>query[i].L )
        {
            L--;
            int x=sum(a[L]);
            int w=sum2(a[L]);
            result+=w+(R-L+1-x)*a[L];
            add(a[L],1);
            add2(a[L],a[L]);
        }
        while( L<query[i].L )
        {
            int x=sum(a[L]);
            int w=sum2(a[L]);
            result-=w+(R-L+1-x)*a[L];
            add(a[L],-1);
            add2(a[L],-a[L]);
            L++;
        }
        ans[ query[i].id ]=result;
    }


}


int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        memset(bit1,0,sizeof(bit1));
        memset(bit2,0,sizeof(bit2));
        scanf("%d%d",&n,&m);
        for(int i=1; i<=n; i++)
        {
            scanf("%d",&a[i]);
        }
        for(int i=0; i<m; i++)
        {
            scanf("%d%d",&query[i].L,&query[i].R);
            query[i].id=i;
            query[i].L;
        }
        unit=(int ) sqrt(n);
        sort(query,query+m,cmp);
        work();
        for(int i=0; i<m; i++)
        {
            cout<<ans[i]<<endl;
        }
    }

    return 0;
}






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值