Chika and Friendly Pairs(莫队+树状数组+数据离散化)
题目
http://acm.hdu.edu.cn/showproblem.php?pid=6534
题意
给你n(1≤n≤27000),m(1≤m≤27000),k (1≤k≤109)三个数。n代表有n个小于109的数,m代表m次询问,每次询问要求查询区间[L,R]的中两个数绝对值差小于等于k的对数一共有多少个。
题解
这个明显是区间查询的题目,很自然的想到莫队算法。可是莫队算法要怎么用呢?
莫队算法关键的点就是add操作和del操作,想明白了这个问题就算解决了。
莫队算法的时间复杂度大概为
O
(
n
n
)
O(n\sqrt{n})
O(nn),所以我们add和del计算答案的过程必须是
O
(
l
o
g
n
)
O(logn)
O(logn)级别才能解决问题。
那我们把问题转移一下: 给你n个数,再增加一个数x,怎么判断这个数和多少个数的绝对值之差小于等于k?
那么我们肯定是查看一共有多少个数在范围[x-k,x+k]的。很显然,这个可以用权值线段树或者树状数组实现,我们只需要query(x+k)-query(x-k-1)就可以计算有多少个数在区间[x-k,x+k]内了。
但是,每个数都是109,k的范围也是109,我们不可能开3*109空间的数组,所以在这里我们需要使用数据离散化,把每个数a[i],a[i]+k,a[i]-k-1保存在数组里,用下标去代替他们的值,这样就能解决问题了。
AC代码
#include<bits/stdc++.h>
#define ll long long
const int maxn = 3e4+5;
using namespace std;
struct mo
{
int l,r,id;
} q[maxn];
int pos[maxn];
ll ans=0;
ll a[maxn];
ll upk[maxn],lowk[maxn],b[maxn];
ll tree[maxn*3];
ll tot[maxn];
vector<ll>v;
void update(int x,ll val)// 树状数组增加的操作
{
while(x<=maxn*3)
{
tree[x]+=val;
x += (x&-x);
}
}
ll query(int x)
{
ll sum=0;
while(x)
{
sum+=tree[x];
x-=(x&-x);
}
return sum;
}
void add(int i) // 莫队增加的操作
{
ans += query(upk[i])-query(lowk[i]);
update(b[i],1);
}
void del(int i)
{
update(b[i],-1);
ans -= query(upk[i])-query(lowk[i]);
}
bool cmp(mo a,mo b) //进行分块排序
{
return pos[a.l]==pos[b.l]? a.r<b.r : a.l<b.l;
}
int main()
{
ll n,m,k;
while(~scanf("%lld %lld %lld",&n,&m,&k))
{
memset(tree,0,sizeof(tree));
ans=0;
ll u=sqrt(n);
for(int i=1; i<=n; i++)
{
scanf("%lld",&a[i]);
pos[i]=i/u; //所分块的位置
v.push_back(a[i]);
v.push_back(a[i]+k);
v.push_back(a[i]-k-1);
}
//对于所有的x,x+k,x-k离散化
sort(v.begin(),v.end());//排序
v.erase(unique(v.begin(),v.end()),v.end());//去重
// for(auto x:v)
// printf("%d ",x);
// puts("");
for(int i=1,p; i<=n; i++)
{
p = lower_bound(v.begin(),v.end(),a[i])-v.begin()+1;
b[i] = p;
p = lower_bound(v.begin(),v.end(),a[i]+k)-v.begin()+1;
upk[i] = p;
p = lower_bound(v.begin(),v.end(),a[i]-k-1)-v.begin()+1;
lowk[i] = p;
}
for(int i=1; i<=m; i++)
{
scanf("%d %d",&q[i].l,&q[i].r);
q[i].id=i;
}
ll l=1,r=0;
sort(q+1,q+1+m,cmp);
//莫队
for(int i=1; i<=m; i++)
{
//如果左指针比左区间小的话,将左指针向右移,作减少函数,下面同理
while(l<q[i].l) del(l++);
while(l>q[i].l) add(--l);
while(r>q[i].r) del(r--);
while(r<q[i].r) add(++r);
tot[q[i].id]=ans;
}
for(int i=1; i<=m; i++)
{
printf("%lld\n",tot[i]);
}
}
}