BZOJ4939: [Ynoi2016]掉进兔子洞

BZOJ

题意

给你一个长度为 n n n的数列,每次询问三个区间共有的数的个数;

题解

对于这种求交或求并的问题,往往会先考虑到使用bitset,但是本题的数据范围太大需要离散化,且bitset无法记录数量,这里就只能在离散化的时候用一点小技巧了:对于重复出现的数我们不需要unique,假如某数第一次出现的编号为 x x x,那么第二次出现它的编号就可以设为 x + 1 x+1 x+1,以此类推,对于这道题,每个区间独立出来考虑,某数在一区间中第一次出现就用它的 x x x编号,第二次出现就用 x + 1 x+1 x+1,这样就相当于把同样的数看做了不同,bitset也就能发挥它的作用了;现在就有了这么一个做法,如果能得到三个区间的数的集合,再 a n d and and一下,就能知道共有的数的个数了,但是有多组询问,该怎么得到每一个询问的三个区间的数集呢,考虑使用莫队,每次区间扩大或缩小时我们都能只花费 O ( n 32 ) O(\frac{n}{32}) O(32n)的代价,就能再原区间的基础上得到新的区间的数集,再把一个询问看成三个区间分别询问,最后再 a n d and and到一起,便能轻松解决本题了;但是一算空间,超了怎么办?把 Q Q Q次询问分三次来进行,这样就只用一次性开 Q 3 \frac{Q}{3} 3Q个bitset了,空间时间也容得下;但是注意离散化时不能加 l o g log log;

#include<bits/stdc++.h>
#define Fst first
#define Snd second
#define RG register
#define mp make_pair
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef long long LL;
typedef long double LD;
typedef unsigned int UI;
typedef unsigned long long ULL;
template<typename T> inline void read(T& x) {
    char c = getchar();
    bool f = false;
    for (x = 0; !isdigit(c); c = getchar()) {
        if (c == '-') {
            f = true;
        }
    }
    for (; isdigit(c); c = getchar()) {
        x = x * 10 + c - '0';
    }
    if (f) {
        x = -x;
    }
}
template<typename T, typename... U> inline void read(T& x, U& ... y) {
    read(x), read(y...);
}
const int N=1e5+10;
int n;
int W[N],V[N],CT[N],part[N],ANS[N/3],S[N],M[N];
bitset<N> G,F[N/3];
struct Data {
    int l,r,id;
}Q[N];
bool cmp1(int A,int B) {
    return W[A]<W[B];
}
bool cmp2(Data A,Data B) {
    return part[A.l]==part[B.l]?A.r<B.r:part[A.l]<part[B.l];
}
void Move(int pos,int val) {
    int t=M[pos];
    if(val<0) G.reset(S[t]+CT[t]-1);
    CT[t]+=val;
    if(val>0) G.set(S[t]+CT[t]-1);
}
void Solve(int m) {
    if(!m) return;
    int cnt=0;
    for(int i=1;i<=m;++i) {
        F[i].set(); ANS[i]=0;
        for(int j=0;j<3;++j) ++cnt,read(Q[cnt].l,Q[cnt].r),Q[cnt].id=i,ANS[i]+=Q[cnt].r-Q[cnt].l+1;
    }
    sort(Q+1,Q+cnt+1,cmp2);
    G.reset(); mem(CT,0);
    for(int i=1,L=1,R=0;i<=cnt;++i) {
        while(R<Q[i].r) Move(++R,1);
        while(R>Q[i].r) Move(R--,-1);
        while(L<Q[i].l) Move(L++,-1);
        while(L>Q[i].l) Move(--L,1);
        F[Q[i].id]&=G;
    }
    for(int i=1;i<=m;++i) printf("%d\n",ANS[i]-3*(int)F[i].count());
}
//#define rua
int main() {
#ifdef rua
    freopen("GG.out","w",stdout);
#endif
    int m; read(n,m);
    int Base=sqrt(n+1),cnt=1,ID=1;
    for(int i=1;i<=n;++i) {
        read(W[i]); part[i]=ID;
        if(cnt==Base) cnt=1,++ID;
        else ++cnt;
        V[i]=i;
    }
    sort(V+1,V+n+1,cmp1);
    for(int i=1,t=0;i<=n;++i) {
        if(W[V[i]]!=W[V[i-1]]) ++t,S[t]=i;
        M[V[i]]=t;
    }
    Solve(min(33334,m));
    if(m>=33334) m-=33334;
    else m=0;
    Solve(min(33333,m));
    if(m>=33333) m-=33333;
    else m=0;
    Solve(min(33333,m));
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值