HDU 5213 Lucky(容斥原理+莫队算法)

来源:互联网 发布:程序员眼里只有两种人 编辑:程序博客网 时间:2024/06/08 07:58

Lucky

Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)
Total Submission(s): 1230    Accepted Submission(s): 441

Problem Descriptio

WLD is always very lucky.His secret is a lucky number K.k is a fixed odd number. Now he meets a stranger with N numbers:a1,a2,...,aN.The stranger asks him M questions.Each question is like this:Given two ranges [Li,Ri] and [Ui,Vi],you can choose two numbers X and Y to make aX+aY=K.TheX you can choose is between Li and Ri and the Y you can choose is between Ui and Vi.How many pairs of numbers(X,Y) you can choose?
If WLD can answer all the questions correctly,he'll be the luckiest man in the world.Can you help him?

Input

There are multiple cases.(At MOST 5)
For each case:
The first line contains an integer N(1N30000).
The following line contains an integer K(2K2N),WLD's lucky number.K is odd.
The following line contains N integers a1,a2,...,aN(1aiN).
The following line contains an integer M(1M30000),the sum of the questions WLD has to answer.
The following M lines,the i-th line contains 4 numbers Li,Ri,Ui,Vi(1LiRi<UiViN),describing the i-th question the stranger asks.

Output

For each case:
Print the total of pairs WLD can choose for each question.

Sample Input

53 1 2 1 2 31 1 2 3 5

Sample Output

2
Hint
a1+a4=a2+a3=3=K.So we have two pairs of numbers (1,4) and (2,3).Good luck!

Source

BestCoder Round #39 ($)



        其实读完题,想了一下没有什么优化思路,就想到大概是莫队算法了。

        大致题意是给你一个数列{ai},和一个固定的奇数K,然后很多个询问,每个询问给出两个区间[l,r]和[u,v],然后问满足ai+aj=K的数对(i,j)有几对,其中i在区间[l,r]中,j在区间[u,v]中。

        虽然说知道了要用莫队,但是实际上,这里一个询问有两个区间,这应该如何应对呢?用两个关键字莫队?后来才知道,此题符合容斥原理。我们考虑一个询问(l,r,u,v)我们可以拆开来看。首先有(l,v),即在整个大区间里面任意取两个a使得等式成立。然后我们考虑这个多计算了什么。由于我们一个数字是[l,r]中取,另一个在[u,v]中取,那么这样计算就多把以下几个部分计算了:一个在[l,r]中,另一个在[r+1,u-1]中;两个都在[l,r]中;一个在[u,v]中,一个在[r+1,u-1]中;两个都在[r+1,u-1]中。两个都在[u,v]中。现在要考虑的就是把多算的减去。前两个部分和最后一部分可以看作一个区间[l,u-1],即在区间中任取;后面三部分可以看作一个区间[r+1,v],即在区间中任取。那么这样就减去了多计算的部分,但是同时又多减去了最后一部分,即两个都在区间[r+1,u-1]中。于是在把这个部分加回去即可。最后总的表达式就是f(l,v)-f(l,u-1)-f(r+1,v)+f(r+1,u-1)。

        最后就是莫队的转移了,由于只是两个数字相加,我们可以用一个桶来记录每一个数字出现的次数,如果加入一个数字i,那么产生的贡献就是K-i出现的次数。用莫队的套路去做即可,注意要判断K-i的大小,防止越界。具体见代码:

#include<bits/stdc++.h>#define N 30010using namespace std;struct query{int l,r,sgn,id;} q[N<<2];int n,k,m,tot,blocks,a[N],ans[N];int mp[N];bool cmp(query a,query b){    return a.l/blocks==b.l/blocks?a.r<b.r:a.l/blocks<b.l/blocks;}int main(){    while(~scanf("%d%d",&n,&k))    {        blocks=sqrt(n+0.5); tot=0;        for(int i=1;i<=n;i++)        {            scanf("%d",&a[i]);            mp[i]=0;        }        scanf("%d",&m);        for(int i=1;i<=m;i++)        {            int l,r,u,v; ans[i]=0;            scanf("%d%d%d%d",&l,&r,&u,&v);            q[++tot]=query{l,v,1,i};//分成四个部分容斥原理            q[++tot]=query{l,u-1,-1,i};            q[++tot]=query{r+1,v,-1,i};            q[++tot]=query{r+1,u-1,1,i};        }        sort(q+1,q+tot+1,cmp);        int l=1,r=0,res=0;        for(int i=1;i<=tot;i++)//莫队基本套路        {            while(l<q[i].l)            {                mp[a[l]]--;                if (k>a[l]&&k-a[l]<=n) res-=mp[k-a[l]];//判断K-i是否在范围内                l++;            }            while(l>q[i].l)            {                l--;                if (k>a[l]&&k-a[l]<=n) res+=mp[k-a[l]];                mp[a[l]]++;            }            while(r<q[i].r)            {                r++;                if (k>a[r]&&k-a[r]<=n) res+=mp[k-a[r]];                mp[a[r]]++;            }            while(r>q[i].r)            {                mp[a[r]]--;                if (k>a[r]&&k-a[r]<=n) res-=mp[k-a[r]];                r--;            }            ans[q[i].id]+=q[i].sgn*res;//sgn表示当前小询问对最后大询问贡献的符号        }        for(int i=1;i<=m;i++)            printf("%d\n",ans[i]);    }    return 0;}

原创粉丝点击