YNOI2016:掉进兔子洞 (莫队+bitset)

YNOI2016:掉进兔子洞

题意简述:

有 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 $ 。



$ solution: $

考场上觉得是毒瘤容斥+莫队(先求两两区间,得到三个区间),但是这道题不能这样容斥,有两个未知量。

其实重点就在于序列的存储的方式,然后就是我们对于莫队+ $ bitset $ 的熟悉度。我们知道如果相同元素在单个区间里只出现一次,那么我们直接对每个区间是否拥有某个元素二进制状压,然后三个区间与运算得到重复元素的信息(可以用 $ bitset $ 维护)。但是相同元素在单个区间里会出现多次,这个有一个很妙的维护方法:我们排序将相同元素放一块,然后对于每一段相同元素记录第一个位置,于是我们在 $ bitset $ 数组中就可以用对应的一段区间来维护这种相同元素出现次数(从区间第一个位置开始,每加入一个这个元素,就将后一个位置变为1)

然后多个 $ bitset $ 与运算,为一的位置说明在多个区间都有这个元素。对于区间的二进制状压信息,我们可以用莫队算法来求,因为对每个询问都要 $ bitset $ 数组,时间没问题但空间开不下,于是将询问分组做。



$ code: $

#include<iostream>
#include<cstdio>
#include<iomanip>
#include<algorithm>
#include<bitset>
#define ll long long
#define rg register int

using namespace std;

int n,m,M,ff;
int a[100005];
int p[100005];
int A[100005];
int ans[100005];
bool vis[25005];
bitset<100005> b[25005],s;

struct su{
	int x,y,id,v;
	inline bool operator <(const su &i){
		if(v==i.v){
			if(v&1) return y<i.y;
			return y>i.y; //奇偶分块优化常数
		} return v<i.v;
	}
}q[100005];

inline int qr(){
    register char ch; register bool sign=0; rg res=0;
    while(!isdigit(ch=getchar()))if(ch=='-')sign=1;
    while(isdigit(ch))res=res*10+(ch^48),ch=getchar();
    if(sign)return -res; else return res;
}

int main(){
	n=qr(); M=m=qr(); ff=pow(n-1,0.5)+1; //分块
	for(rg i=1;i<=n;++i) A[i]=a[i]=qr();
	sort(A+1,A+n+1); //离散化,顺带把相同元素放一起并记录第一个位置
	for(rg i=1;i<=n;++i) a[i]=lower_bound(A+1,A+n+1,a[i])-A;
	while(M){
		m=min(M,25000); M-=m; rg tt=0; //问询分组处理
		for(rg i=1;i<=n;++i) p[a[i]]=a[i];
		for(rg i=1;i<=m;++i){
			rg l1=qr(),r1=qr(),l2=qr(),r2=qr(),l3=qr(),r3=qr();
			ans[i]=r1+r2+r3-l1-l2-l3+3; vis[i]=0;
			q[++tt]=su{l1,r1,i,(l1-1)/ff+1}; //最后一个元素分块
			q[++tt]=su{l2,r2,i,(l2-1)/ff+1};
			q[++tt]=su{l3,r3,i,(l3-1)/ff+1};
		} sort(q+1,q+tt+1); //分块排序
		rg l=1,r=0; s.reset();
		for(rg i=1;i<=tt;++i){
			rg x=q[i].x,y=q[i].y,id=q[i].id;
			while(x<l)--l,s[p[a[l]]++]=1; //莫队
			while(r<y)++r,s[p[a[r]]++]=1;
			while(l<x)s[--p[a[l]]]=0,++l;
			while(y<r)s[--p[a[r]]]=0,--r;
			if(vis[id]) b[id]&=s; //&,三个集合都要有
			else vis[id]=1,b[id]=s; //第一个直接覆盖(都不需要预处理)
		}
		for(rg i=1;i<=m;++i) //答案=总数-不合法数
			printf("%d\n",ans[i]-(int)b[i].count()*3);
	}
    return 0;
}

posted @ 2019-10-20 21:10  一只不咕鸟  阅读(362)  评论(0编辑  收藏  举报