2021杭电多校第二场1004.I love counting

1004.I love counting

补题地址

题目大意:

​ 给出n个数,q次询问,每次询问l-r区间有多少个不同的数异或a小于b
1 ≤ n , q ≤ 1 0 5 , 1 ≤ l ≤ r ≤ n , 0 ≤ a , b ≤ n + 1 1 \leq n,q\leq10^5,1 \leq l \leq r\leq n,0 \leq a,b\leq n+1 1n,q105,1lrn,0a,bn+1

题解:

​ 先考虑这样一个子问题:

​ n个数中有多少个数异或a小于b?(先不考虑l,r与“不同”这两个条件)

子问题解:

先将n个数按照值进行统计,然后从高位到低位分别讨论a,b的情况,进行计数。

举例说明:(以下以二进制表示数,最低位为第0位)

a=11001,b=01100

第4位b为0,a为1,所以第4位我们只能填1才能继续填下去,填0的话异或上a就肯定大于b了

第3位b为1,a为1,该位填1的话异或上a肯定小于b,贡献为值在11000-11111的数的个数,填0的话接着统计

第2为b为1,a为0, 该位填0的话异或上a肯定小于b,贡献为值在10000-10011的数的个数,填1的话继续统计

第1位b为0,a为0,只好填0

第0位b为0,a为1只好填1

所以总的贡献就是值在[11000,11111] ⋃ \bigcup [10000,10011] ⋃ \bigcup {10101(a^b)} 中数的个数。

回到原问题:

我们可以考虑将[l,r]中的数放到某种数据结构S中。

解决了该子问题之后,我们可以使用莫队+哈希数组(记录每个数出现的次数)的办法来保证S中的数都在[l,r]内,并且都是无重的

对于每一个询问讨论a,b的每个位进行区间查询即可

那么我们就需要一个单点修改区间查询的数据结构S。

我们知道修改的次数是 n ∗ n n*\sqrt n nn 次(不妨设q=n)

而查询的次数最大是n*log次。

重要的是通过上面的分析我们可知查询具有极强的特点,每次查询的区间大小都是2的幂次,对于每个数的查询区间都是由大到小

我们可以选取分块来充当S,这样的话修改的复杂度是O(1),并且由于询问区间的性质导致查询平摊的复杂度很低。

对于单个询问复杂度的粗略证明:

​ 对于每次询问只考虑左端点,左端点距离所处块的右端点的距离的期望显然是 b l o c k 2 \frac{block}{2} 2block(block为块的大小,通常设置成 n \sqrt n n ,右端点距离所处块的左端点的距离的期望也是 b l o c k 2 \frac{block}{2} 2block ,其查询的长度为2的次幂,如果左右端点在一个块内遍历即可, i ≤ 8 i\leq 8 i8时在同一个块内。

那么对于单次查询复杂度为:
∑ i = 9 17 ( 2 i b l o c k + b l o c k ) + ∑ i = 1 8 2 i \sum_{i=9}^{17} (\frac{2^i}{block}+block) + \sum_{i=1}^8 2^i i=917(block2i+block)+i=182i
n = 1 0 5 n=10^5 n=105时其值为3400左右,如果考虑每次询问分块的复杂度都是分块的上限即block,那么单次询问的复杂度为 5700左右,由理论值即可看出询问的性质对分块的复杂度影响很大。

总复杂度为O( n ∗ n + n ∗ ( ∑ i = 9 17 ( 2 i b l o c k + b l o c k ) + ∑ i = 1 8 2 i ) n*\sqrt n+n*(\sum_{i=9}^{17} (\frac{2^i}{block}+block) + \sum_{i=1}^8 2^i) nn +n(i=917(block2i+block)+i=182i))

n = 1 0 5 n=10^5 n=105时,其值为 3 ∗ 1 0 8 3*10^8 3108

该题时间限制是2s.

以上的计算只是理论值,由于实际上有很多情况是不需要询问的,实际测试时单次询问平均复杂度仅在1300左右,峰值在3000左右(和理论值很接近),并且询问分块和里面涉及的都是加法,跑的比较快。

我试图构造全是峰值的数据卡掉自己的程序,没有成功,O2优化下仅用了不到1s就跑出了结果(虽然复杂度计数器真的跑到了3e8,但是真的跑到很快,这和分块查询中的计算几乎全是加法关系很大)。

如果数据结构使用线段树或者树状数组以及字典树,那么修改的复杂度是logn的,一定会超时。

这启发我们在修改次数很多而求和相对比较少时使用分块算法。

AC代码如下:

#include <bits/stdc++.h>
#define debug(x) cout<<#x"=" <<x<<'\n';
using ll=long long;
using namespace std;
template<typename T> void read(T &x){
    x = 0;char ch = getchar();ll f = 1;
    while(!isdigit(ch)){if(ch == '-')f*=-1;ch=getchar();}
    while(isdigit(ch)){x = x*10+ch-48;ch=getchar();}x*=f;
}
const int M=1e5+5;
int block;///块的大小,记得计算
struct node{
    int l,r,id,a,b;
    bool operator < (const node &cmp) const{
         if(l/block == cmp.l/block)  return r < cmp.r;
         return l/block < cmp.l/block;
    }
}q[M];
struct BLOCK
{
    int sum[M],a[M<<1];
    void add(const int &x,const int &u) ///在x的位置加上y
    {
        //debug(x);
        //debug(u);
        sum[x/block]+=u;
        a[x]+=u;
    }
    int ask(const int &l,const int &r)///询问[l,r]的和
    {
        //debug(l);
        //debug(r);
        int ans=0;
        if(r-l<=block)
        {
            for(int i=l;i<=r;i++) ans+=a[i];
        //debug(ans);
            return ans;
        }
        int bl=(l/block+1)*block;
        int br=r/block*block;
        for(int i=l;i<bl;i++) ans+=a[i];
        for(int i=br;i<=r;i++) ans+=a[i];
        int blockl=l/block+1;
        int blockr=r/block-1;
        for(int i=blockl;i<=blockr;i++) ans+=sum[i];
        return ans;
    }
}B;
int a[M];
int num[M];
int hs[M];
inline void  add(int x)
{
    x=a[x];
    if(hs[x]==0)
        B.add(x,1);
    hs[x]++;
}
inline void del(int x)
{
    x=a[x];
    if(hs[x]==1)
        B.add(x,-1);
    hs[x]--;
}
inline int ask(const int &a,const int &b)
{

    int ans=0;
    int now=0;
    for(int i=17;i>=0;i--)
    {
        if(b>>i &1)
        {
            if(a>>i & 1)
            {
                ans+=B.ask(now+(1<<i),now+(1<<(i+1))-1);
            }
            else ans+=B.ask(now,now+(1<<i)-1);
        }
        if((a>>i &1) ^ (b>>i &1)) now|=1<<i;
    }
    ans+=B.ask(a^b,a^b);
    return ans;
}
int main() {

    int n;
    read(n);
    block=sqrt(n);
    for(int i=1;i<=n;i++) read(a[i]);
    int m;
    read(m);
    for(int i=1;i<=m;i++)
    {
        read(q[i].l),read(q[i].r),read(q[i].a),read(q[i].b);
        q[i].id=i;
    }
    sort(q+1,q+m+1);
    int l = 1,r = 0;
    for(int i = 1;i <= m;i ++){

        while(r < q[i].r) add(++r);
        while(r > q[i].r) del(r--);
        while(l < q[i].l) del(l++);
        while(l > q[i].l) add(--l);
        num[q[i].id]=ask(q[i].a,q[i].b);
    }
    for(int i = 1;i <= m;i ++) printf("%d\n",num[i]);
    return 0;
}
/*
5
1 2 2 4 5
1
1 3 1 3
*/

参考资料:

某大佬博客: 2021杭电多校2 (asukakyle.top)

思维历程

比赛时一直在想要到字典树上去做询问,而没有进一步分析"多少个数异或a小于b"这个条件,没有将其转化为单点修改,区间求和问题。

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值