当前位置: 代码迷 >> 综合 >> 4939: [Ynoi2016]掉进兔子洞
  详细解决方案

4939: [Ynoi2016]掉进兔子洞

热度:41   发布时间:2023-10-29 10:45:42.0

题意

题面很长,取重要部分就好了
一个长为 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。

题解

这题看了路牌——莫队,什么想法都没有
三个区间莫什么队啊。。
然后我们可以稍作思考,其实答案就是三个区间的个数和-公共部分嘛。。
于是就是要就公共部分
看了看题解,才知道怎么做。。感觉并不难
公共部分其实我们是可以用bitset来维护的
对于每一个数字,一开始的下标为x
第一次的时候就标记x出现了,第二次标记为x+1出现了。。如此类推
就可以解决一个数字出现多次的情况了。。
然后答案就是三个bitset与一下,看一下1的个数就可以了
然后呢,听说这题卡常数(其实我觉得这复杂度本来就有点大,但是给了80s的时限还可以吧),于是要手写bieset
这里大概说一下手写的bitset怎么写:
bitset其实就是bool数组,但是这样太大/慢了
我们考虑将bool数组开为int
然后就可以位压了
每32位压一压。。
这一块一起处理,这样时空复杂度都是1/32了

CODE:(时空垫底)

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<cmath>
using namespace std;
typedef unsigned int Int;
const int N=100005 ;
const int M=33334;//这么多操作做一次 
int n,m,nn;
int cal[(1<<16)+10];//这个数有多少个1 
int belong[N];
struct qa{
   int x,id;}a[N];
int A[N];//离散化后的数组 
int b[N];
int head[N];//离散化后这个值在哪里 
inline int read()
{int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){
   if(ch=='-')f=-1;ch=getchar();}while(ch>='0'&&ch<='9'){
   x=x*10+ch-'0';ch=getchar();}return x*f;
}
bool cmp (qa x,qa y){
   return x.x<y.x;}
int l1[N],r1[N],l2[N],r2[N],l3[N],r3[N];
int ans[N];
void Init ()
{n=read();m=read();nn=sqrt(n);for (int u=0;u<(1<<16);u++) cal[u]=cal[u>>1]+(u&1);for (int u=1;u<=n;u++) belong[u]=(u-1)/nn+1;for (int u=1;u<=n;u++)  {a[u].x=read();a[u].id=u;}sort(a+1,a+1+n,cmp);int tot=1;A[a[1].id]=tot;for (int u=2;u<=n;u++){if (a[u].x!=a[u-1].x) tot++;A[a[u].id]=tot;}for (int u=1;u<=n;u++) b[u]=A[u];sort(b+1,b+1+n);for (int u=1;u<=n;u++)if (b[u]!=b[u-1])head[b[u]]=u;for (int u=1;u<=m;u++){l1[u]=read();r1[u]=read();l2[u]=read();r2[u]=read();l3[u]=read();r3[u]=read();ans[u]=(r1[u]-l1[u]+1)+(r2[u]-l2[u]+1)+(r3[u]-l3[u]+1);}
}
struct qq {int l,r,id; }q[(M+10)*3];int len;
bool cmp1 (qq x,qq y)
{return belong[x.l]==belong[y.l]?x.r<y.r:belong[x.l]<belong[y.l];
}
struct bs //手写bitset 
{Int S[3125];inline void vis (int x)//如果之前存在就把它加上,否则减去 {S[x>>5]^=(1U<<(x&31));}int count ()//有多少个1{int tot=0;for (int u=0;u<=3124;u++)tot=tot+cal[S[u]>>16]+cal[S[u]&65535];return tot;}void clear (){memset(S,0,sizeof(S));}
}now,c[M+10];
bs operator ^ (bs x,bs y)
{bs z;for (int u=0;u<=3124;u++)   z.S[u]=(x.S[u]^y.S[u]);return z;
}
bs operator & (bs x,bs y)
{bs z;for (int u=0;u<=3124;u++)   z.S[u]=(x.S[u]&y.S[u]);return z;
}
int h[N];//每个位置使用到的下标 
void Del (int x){now.vis(head[x]+h[x]-2);h[x]--;}
void Add (int x){h[x]++;now.vis(head[x]+h[x]-2);}
void solve1 ()
{now.clear();memset(h,0,sizeof(h));memset(c,255,sizeof(c));//全部清为1int l=1,r=0;sort(q+1,q+1+len,cmp1); /*for (int u=1;u<=len;u++)printf("YES:%d %d %d\n",q[u].l,q[u].r,q[u].id);*/for (int u=1;u<=len;u++){   while (l>q[u].l) Add(A[--l]);while (l<q[u].l) Del(A[l++]);while (r>q[u].r) Del(A[r--]);while (r<q[u].r) Add(A[++r]);c[q[u].id]=c[q[u].id]&now;//printf("%d %d\n",q[u].id,c[q[u].id].count());}
}
void solve ()
{for (int u=1;u<=m;u+=M){len=0;for (int i=u;i<=u+M-1;i++){if (i>m) break;q[++len]=qq{l1[i],r1[i],i-u+1};q[++len]=qq{l2[i],r2[i],i-u+1};q[++len]=qq{l3[i],r3[i],i-u+1};}solve1();for (int i=u;i<=u+M-1;i++){if (i>m) break;printf("%d\n",ans[i]-3*c[i-u+1].count());}}
}
int main()
{Init();solve();return 0;
}