あいさか たいがblogAisaka_Taiga的博客
//https://img2018.cnblogs.com/blog/1646268/201908/1646268-20190806114008215-138720377.jpg

P4688 [Ynoi2016] 掉进兔子洞

Toretto·2023-04-01 16:13·54 次阅读

P4688 [Ynoi2016] 掉进兔子洞

RE了大约12次以后,SoN3ri告诉我是bitset开小了。

image

那你为什么全RE了啊(?

题意是给你一个长度为 n 的序列,一共 m 次询问,每次询问包含三个区间,求三个区间内相同的数去掉后剩下的数的个数。

做完了小清新人渣的本愿,看啥都是bitset+莫队,这题我也是一开始打了一个莫队+bitset,但是很不幸我因为开小了RE了12次,但这都不重要了,来看一下如何做这道题目。

首先看到值域很大,所以我们先离散化,然后对于每一个询问,我们都开一个bitset sum[N],来存放每一次询问中重复的元素的个数,我们发现题目解释里面举得例子,要求不是每一个区间去重后再进行查询,也就是说,我们不能在离散化的时候去重,这样我们在添加和删除一个值对答案的贡献的时候,我们就可以直接用第 x+cnt[x] 位来表示当前点是否重合。在进行对于答案的计算时,我们假设一开始所有的数都是不重合的,然后把 sum[i] 给全赋成1(因为后面是要进行取与运算的),表示一开始都重合,然后三个区间和 sum[i] 取与,得到的里面1的个数就是重合的元素的个数。

然后我们就可以写出下面的代码:

Copy
#include<bits/stdc++.h> #define N 100100 using namespace std; int n,m,kc,a[N],bl[N],ans[N],cnt[N]; bitset<N>sum[N],now; struct sb{int l,r,id;}e[N]; inline int read(){int x=0,f=1;char ch=getchar();while(!isdigit(ch)){f=ch!='-';ch=getchar();}while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return f?x:-x;} inline int cmp(sb a,sb b){if(bl[a.l]==bl[b.l])return a.r<b.r;return a.l<b.l;} inline void add(int x){now.set(x+cnt[x]);cnt[x]++;} inline void del(int x){cnt[x]--;now.reset(x+cnt[x]);} signed main() { n=read();m=read(); kc=sqrt(n); for(int i=1;i<=n;i++) a[i]=read(),bl[i]=(i-1)/kc+1; int tmp[N]; memcpy(tmp,a,sizeof(a)); sort(tmp+1,tmp+n+1); for(int i=1;i<=n;i++) a[i]=lower_bound(tmp+1,tmp+n+1,a[i])-tmp; int tot=0; now.reset(); for(int i=1;i<=m;i++) { ans[i]=0; sum[i].set(); for(int j=1;j<=3;j++) { e[++tot].l=read();e[tot].r=read(); e[tot].id=i; ans[i]+=e[tot].r-e[tot].l+1; } } sort(e+1,e+tot+1,cmp); int nl=1,nr=0; for(int i=1;i<=tot;i++) { while(nl>e[i].l)add(a[--nl]); while(nr<e[i].r)add(a[++nr]); while(nl<e[i].l)del(a[nl++]); while(nr>e[i].r)del(a[nr--]); sum[e[i].id]&=now; } for(int i=1;i<=m;i++) cout<<ans[i]-(int)sum[i].count()*3<<endl; return 0; }

然后我就从RE变成了:
image

开这么多不MLE才怪。。。。

然后稍加思索,我们可以把询问“分块”处理,把他分成几组小的分别处理就好了。
时限三秒放心memset。

Copy
#include<bits/stdc++.h> #define N 100100 #define M 25100 using namespace std; int n,m,kc,a[N],bl[N],ans[N],cnt[N]; bitset<N>sum[M],now; struct sb{int l,r,id;}e[N]; inline int read(){int x=0,f=1;char ch=getchar();while(!isdigit(ch)){f=ch!='-';ch=getchar();}while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return f?x:-x;} inline int cmp(sb a,sb b){if(bl[a.l]==bl[b.l])return a.r<b.r;return a.l<b.l;} inline void add(int x){now.set(x+cnt[x]);cnt[x]++;}//标记当前点的值出现 inline void del(int x){cnt[x]--;now.reset(x+cnt[x]);}//标记当前点的值去除 inline void solve() { if(m==0)return ; //如果之前就已经全完成了就直接退出 int tot=0,i=1; now.reset();//清空now数组 for(i=1;i<=M-10;i++)//一次最多进行25000次查询操作 { if(m==0)break;//如果进行完了就退出 m--; ans[i]=0;//清空当前询问的答案 sum[i].set();//当前询问的集合全设为1 for(int j=1;j<=3;j++)//三个区间 { e[++tot].l=read();e[tot].r=read();//输入区间信息 e[tot].id=i;//标号 ans[i]+=e[tot].r-e[tot].l+1;//累加当前点的答案,假设一开始全不重合 } } sort(e+1,e+tot+1,cmp);//询问排序 int nl=1,nr=0,t=i-1;//t是当前询问的个数 for(int i=1;i<=tot;i++) { while(nl>e[i].l)add(a[--nl]);//正常莫队 while(nr<e[i].r)add(a[++nr]); while(nl<e[i].l)del(a[nl++]); while(nr>e[i].r)del(a[nr--]); sum[e[i].id]&=now;//当前序列内的元素的数量,三个区间都进行过&操作后sum里面存放的就是当前重合的数的离散化后的值 } // cout<<"T:"<<t<<endl; for(int i=1;i<=t;i++) cout<<ans[i]-(int)sum[i].count()*3<<endl;//减去重合的数的数量输出答案 } signed main() { n=read();m=read(); kc=sqrt(n); for(int i=1;i<=n;i++) a[i]=read(),bl[i]=(i-1)/kc+1; int tmp[N]; memcpy(tmp,a,sizeof(a));//复制数组 sort(tmp+1,tmp+n+1);//排序 for(int i=1;i<=n;i++)//离散化 a[i]=lower_bound(tmp+1,tmp+n+1,a[i])-tmp;//直接在原数组上修改 for(int i=1;i<=4;i++)//分四次进行 { solve(); for(int i=1;i<=n;i++) cnt[i]=0; //每次都要清空cnt数组 } return 0; }
posted @   北烛青澜  阅读(54)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示