1. 程式人生 > >HihoCoder 1488 : 排隊接水(莫隊+樹狀數組)

HihoCoder 1488 : 排隊接水(莫隊+樹狀數組)

pri coder esp 適合 兩個 long long 輸入 string sca

描述

有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 : 排隊接水(莫隊+樹狀數組)