P4688 [Ynoi2016] 掉进兔子洞
RE了大约12次以后,SoN3ri告诉我是bitset开小了。
那你为什么全RE了啊(?
题意是给你一个长度为 \(n\) 的序列,一共 \(m\) 次询问,每次询问包含三个区间,求三个区间内相同的数去掉后剩下的数的个数。
做完了小清新人渣的本愿,看啥都是bitset+莫队,这题我也是一开始打了一个莫队+bitset,但是很不幸我因为开小了RE了12次,但这都不重要了,来看一下如何做这道题目。
首先看到值域很大,所以我们先离散化,然后对于每一个询问,我们都开一个bitset sum[N],来存放每一次询问中重复的元素的个数,我们发现题目解释里面举得例子,要求不是每一个区间去重后再进行查询,也就是说,我们不能在离散化的时候去重,这样我们在添加和删除一个值对答案的贡献的时候,我们就可以直接用第 x+cnt[x] 位来表示当前点是否重合。在进行对于答案的计算时,我们假设一开始所有的数都是不重合的,然后把 sum[i] 给全赋成1(因为后面是要进行取与运算的),表示一开始都重合,然后三个区间和 sum[i] 取与,得到的里面1的个数就是重合的元素的个数。
然后我们就可以写出下面的代码:
#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变成了:
开这么多不MLE才怪。。。。
然后稍加思索,我们可以把询问“分块”处理,把他分成几组小的分别处理就好了。
时限三秒放心memset。
#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;
}
本文来自博客园,作者:北烛青澜,转载请注明原文链接:https://www.cnblogs.com/Multitree/p/17278797.html
The heart is higher than the sky, and life is thinner than paper.