• [Ynoi2016]掉进兔子洞


    洛咕

    题意:一个长为(n)的序列(a).有(m)个询问,每次询问三个区间,把三个区间中同时出现的数一个一个删掉,问最后三个区间剩下的数的个数和,询问独立.注意这里删掉指的是一个一个删,不是把等于这个值的数直接删完,比如三个区间是 ([1,2,2,3,3,3,3]),([1,2,2,3,3,3,3])([1,1,2,3,3]),就一起扔掉了(1)(1)(1)(2)(2)(3).

    数据范围:(n,m<=100000,1<=a[i]<=1000000000).

    分析:首先(a[i])肯定要离散化,但是本题的离散化有个特点,设每个数出现了(cnt_i)次,则(a[i])占据的是从所有比它小的数的个数(sum+1)(sum+cnt_i)这一段区间,它们的值都是(sum+1).因为(sum cnt_i=n),所以这样离散化后每个元素的值还是在(n)的范围内.那么如何实现这一点呢,其实就是离散化的时候不要(unique),(sort)之后直接(lower)_(bound)即可.

    对于本题这种很多个区间内一些元素个数的维护,不难想到莫队.毕竟莫队的模板题小Z的袜子就是这样一种题型.

    所以老套路,把长度为n的序列,分成(unit=sqrt n)块,然后把所有询问的区间(3m)个,以(l)为第一关键字,(r)为第二关键字从小到大排序.接下来考虑如何统计答案.

    首先对于每一个询问(i),(ans=r1-l1+1+r2-l2+1+r3-l3+1-3*num).其中(num)表示删掉的元素的个数.所以只要考虑如何求(num).

    对每一个询问开一个长度为(N)(bitset),每一位是否为1表示当前这个询问是否删这个数(显然,M个长度为N的bitset还是会爆空间,所以考虑把M个询问拆成几个部分.)设(sum[i])表示离散化后(i)这个数在当前区间内出现的次数,然后实时维护另一个(bitset):(now),每一位是否为1表示当前这个区间是否有这个数.

    一个询问如果要删这个数,就是这个询问的三个区间的(now)取交(即&运算).之所以这里统计答案能够如此简单,就是因为我们之前的离散化,保证了相同的数在(bitset:now)上占据了一段区间,那么三个取&,就是取最短的那一段区间,是符合题意的.

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<cmath>
    #include<queue>
    #include<map>
    #include<set>
    #include<bitset>
    #define ll long long
    using namespace std;
    inline int read(){
        int x=0,o=1;char ch=getchar();
        while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
        if(ch=='-')o=-1,ch=getchar();
        while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
        return x*o;
    }
    const int N=100005;
    const int M=25005;
    int a[N],b[N],bel[N],sum[N],ans[N],visit[N];
    struct query{int l,r,id;}q[N];
    inline bool cmp(query x,query y){return bel[x.l]==bel[y.l]?x.r<y.r:x.l<y.l;}
    bitset<N>s[M],now;//NM/8个字节,不会爆
    inline void solve(int m){
    	int tot=0;
    	memset(sum,0,sizeof(sum));//忘记初始化这个数组,调了半个小时
    	for(int i=1;i<=m;++i){
    		visit[i]=0;ans[i]=0;//初始化
    		for(int j=1;j<=3;++j){
    			q[++tot].l=read();q[tot].r=read();q[tot].id=i;
    			ans[i]+=q[tot].r-q[tot].l+1;
    		}
    	}
    	sort(q+1,q+tot+1,cmp);now.reset();int l=1,r=0;
    	for(int i=1;i<=tot;++i){
    //四个while的顺序,先扩大区间,即l--和r++,再考虑缩小区间,即l++和r--.
    //刚开始这里随便写的,调了一下午,一直RE
    		while(l>q[i].l)--l,now[a[l]+sum[a[l]]]=1,++sum[a[l]];
    		while(r<q[i].r)++r,now[a[r]+sum[a[r]]]=1,++sum[a[r]];
    		while(r>q[i].r)--sum[a[r]],now[a[r]+sum[a[r]]]=0,--r;
    		while(l<q[i].l)--sum[a[l]],now[a[l]+sum[a[l]]]=0,++l;
    		if(!visit[q[i].id])visit[q[i].id]=1,s[q[i].id]=now;//这个询问之前没访问过,先直接赋值
    		else s[q[i].id]&=now;//否则取交
    	}
    	for(int i=1;i<=m;++i)printf("%d
    ",ans[i]-3*(int)s[i].count());
    }
    int main(){
    	int n=read(),m=read(),unit=sqrt(n);
    	for(int i=1;i<=n;++i)a[i]=read(),b[i]=a[i],bel[i]=i/unit+1;
    	sort(b+1,b+n+1);for(int i=1;i<=n;++i)a[i]=lower_bound(b+1,b+n+1,a[i])-b;//离散化
    	while(m){if(m<=25000)solve(m),m=0;else solve(25000),m-=25000;}//分成每个部分25000个询问来做
        return 0;
    }
    
    
  • 相关阅读:
    信件分析实战(五)——数据可视化
    信件分析实战(四)——数据分析以及部分可视化
    剑指offer15题
    剑指offer14题
    剑指offer11题
    剑指offer第9题
    剑指offer第8题--动态规划最简单讲解
    剑指offer第7题
    剑指offer第6题
    剑指offer第5题
  • 原文地址:https://www.cnblogs.com/PPXppx/p/11715631.html
Copyright © 2020-2023  润新知