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; }
It is your time to fight!