zoukankan      html  css  js  c++  java
  • 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
    ",s[i].ans);
        }
        return 0;
    }
  • 相关阅读:
    生成排列与生成子集
    赛后总结AtCoder Beginner Contest 090(Beginner)
    树状数组笔记
    论怎么记住tarjan的板子
    tarjan缩点-受欢迎的牛-笔记
    tarjan模板(%%%hzwer)-2.0
    tarjan模板(%%%hzwer)
    匈牙利算法学习笔记
    最短路-Car的旅行路线
    数据结构 笔记1 搜索树
  • 原文地址:https://www.cnblogs.com/hua-dong/p/8455183.html
Copyright © 2011-2022 走看看