小清新人渣的本愿(莫队+bitset)

小清新人渣的本愿

这两天写了些 b i t s e t bitset bitset的题,但都不想写题解。。。正巧这道题还结合了莫队,也是正在学习的,就记录一下吧。

题意:

给定一个 a a a数组,有三种询问:

  1. 询问 [ l , r ] [l,r] [l,r]区间中是否有差为 x x x的数对;
  2. 询问 [ l , r ] [l,r] [l,r]区间中是否有和为 x x x的数对;
  3. 询问 [ l , r ] [l,r] [l,r]区间中是否有积为 x x x的数对。

思路:

  1. 先只考虑三种询问后面的部分,不考虑区间问题。
  2. 要求是否存在差为 x x x的数对,显然只需要知道每个数向前(或向后) x x x的数值是否存在,要检索所有数字的话,考虑使用 b i t s e t bitset bitset ( b (b (b& b b b<< x ) . a n y x).any x).any()就可以知道是否存在啦!
  3. 要求是否存在和为 x x x的数对,有一个小技巧,同时维护一个反转的 b i t s e t bitset bitset,想想将反转的 b i t s e t bitset bitset向右移动 N N N a a a数组的值域)位,肯定这个 b i t s e t bitset bitset就全为 0 0 0了;但如果少移动 x x x个位置的话,就相当于把原状态中前 [ 0 , x ] [0,x] [0,x]的部分保留了,并且是反转的;显然将原 b i t s e t bitset bitset和这个经过处理的 b i t s e t bitset bitset取与,就可以知道是否有和为 x x x的数对啦!
  4. 而积为 x x x是否存在反而最简单,考虑这个小问题的复杂度, m m m ( ≤ 1 e 5 ) (≤1e5) (1e5)个询问, x x x又是小于 1 e 5 1e5 1e5的,因此枚举 x x x所有较小因数的复杂度为 O ( m ∗ x ) O(m*\sqrt{x}) O(mx ),完全是可以接受的!
  5. 至此,此问题就只剩维护区间状态的问题了;上述三种问题都指向了一种状态——某个数字是否存在,这恰好是莫队的常见问题,不难想出。因此,总复杂度为莫队的端点移动 ∗ * 每个询问的答案判断,为 O ( n 3 2 ∗ ( n + n 32 ) ) O(n^\frac{3}{2}*(\sqrt{n}+\frac{n}{32})) O(n23(n +32n))(其中 m m m a a a的值域都用 n n n代替了),感觉还是能被卡住的。

代码

#include "bits/stdc++.h"
#define hhh printf("hhh\n")
#define see(x) (cerr<<(#x)<<'='<<(x)<<endl)
using namespace std;
typedef long long ll;
typedef pair<int,int> pr;
inline int read() {int x=0;char c=getchar();while(c<'0'||c>'9')c=getchar();while(c>='0'&&c<='9')x=x*10+c-'0',c=getchar();return x;}

const int maxn = 1e5+10;
const int inf = 0x3f3f3f3f;
const int mod = 1e9+7;
const double eps = 1e-7;
const int N = 100001;

int n, m, len;
int a[maxn], cnt[maxn], ans[maxn];
bitset<N+1> b1, b2;

struct P{
    int opt, l, r, x, id;
    friend bool operator < (const P &a, const P &b) {
        if((a.l-1)/len!=(b.l-1)/len) return a.l<b.l;
        if((a.l-1)/len%2) return a.r>b.r;
        return a.r<b.r;
    }
}p[maxn];

inline void del(int x) {
    if(--cnt[x]==0) b1[x]=0, b2[N-x]=0;
}
inline void add(int x) {
    if(cnt[x]++==0) b1[x]=1, b2[N-x]=1;
}

int main() {
    //ios::sync_with_stdio(false); cin.tie(0);
    n=read(), m=read();
    for(int i=1; i<=n; ++i) a[i]=read();
    for(int i=1; i<=m; ++i) {
        int opt=read(), l=read(), r=read(), x=read();
        p[i]=(P){opt,l,r,x,i};
    }
    len=sqrt(m);
    sort(p+1,p+1+m);
    int l=1, r=0;
    for(int i=1; i<=m; ++i) {
        while(l<p[i].l) del(a[l++]);
        while(l>p[i].l) add(a[--l]);
        while(r<p[i].r) add(a[++r]);
        while(r>p[i].r) del(a[r--]);
        if(p[i].opt==1) ans[p[i].id]=(b1&b1<<p[i].x).any()?1:0;
        else if(p[i].opt==2) ans[p[i].id]=(b1&b2>>(N-p[i].x)).any()?1:0;
        else {
            bool f=0;
            for(int j=1; j*j<=p[i].x; ++j) {
                if(p[i].x%j==0&&b1[j]&&b1[p[i].x/j]) { f=1; break; }
            }
            ans[p[i].id]=f;
        }
    }
    for(int i=1; i<=m; ++i) {
        if(ans[i]) printf("hana\n");
        else printf("bi\n");
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值