2019CCPC湘潭全国邀请赛 C.Chika and Friendly Pairs(莫队+树状数组+数据离散化)

14 篇文章 0 订阅
6 篇文章 0 订阅

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]);
        }
    }
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值