省选模拟赛20200418 C、区间(bitset+卡常技巧+卡空间技巧)

 

 

看起来挺正常的一个数据结构题

结果这内存限制:8MB

 

 

题解

显然可以用bitset

而且bitset的空间开销比较小

所以我们可以用分块,对每一个块存一个bitset

块的两端就直接暴力加入bitset

最慢用时6000ms+,得分10pts

这就是我考试时的得分了。。。

 

考虑一下怎么优化

调整一下块的大小,发现调到n/35左右是最优的,大概用时3000ms左右

 

又一个非常有用的优化:

把只出现一次的数单独来计算,发现可以直接用前缀和,注意这里要把查询的区间求一下并集

把剩下的数,离散化之后可以把bitset的大小缩小一半

(这里cheat一下,通过数据,我发现可以直接把bitset大小开到28000)

最慢用时2000ms+

 

省出来看空间怎么用呢?

我们可以把块i~块j的答案都预处理出来,最后就可以直接把ans|=b[i][j]了

最慢用时1500ms+

 

又有了一个新想法

把分块暴力处理的那一部分的时间复杂度减小!

之前剔除已经了只出现一次的数,我们存下剔除之后的数组

二分一下求出每个l,r对应在简化数组中的坐标

在简化数组中进行暴力

最慢用时1300ms+

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<bitset>
using namespace std;
inline int gi()
{
	char c;int num=0,flg=1;
	while((c=getchar())<'0'||c>'9')if(c=='-')flg=-1;
	while(c>='0'&&c<='9'){num=num*10+c-48;c=getchar();}
	return num*flg;
}
#define N 100005
int L;
int a[N],tong[N],sum[N],hh[N],hcnt;
bitset<28000> b[36][36],tmp,ans;
inline int num(int x){return (x-1)/L+1;}
struct node{int l,r;}q[N];
bool cmp(node q,node w){return q.l<w.l||(q.l==w.l&&q.r<w.r);}
int main()
{
	freopen("interval.in","r",stdin);
	freopen("interval.out","w",stdout);
	int n,m,op,i,j,k,l,r,pre=0,o,tt=0;
	n=gi();m=gi();op=gi();
	L=n/35;
	for(i=1;i<=n;i++){a[i]=gi()-1;tong[a[i]]++;}
	for(i=1;i<=n;i++){
		if(tong[a[i]]>1) hh[++hcnt]=a[i];
		else sum[i]++;
		sum[i]+=sum[i-1];
	}
	sort(hh+1,hh+hcnt+1);
	hcnt=unique(hh+1,hh+hcnt+1)-hh-1;
	for(i=1;i<=n;i++)
		if(tong[a[i]]>1)b[num(i)][num(i)].set(a[i]=(lower_bound(hh+1,hh+hcnt+1,a[i])-hh-1));
		else a[i]=-1;
	memset(hh,0,sizeof(hh));tt=0;
	memset(tong,0,sizeof(tong));
	for(i=1;i<=n;i++)
		if(a[i]>=0){
			hh[++tt]=a[i];
			tong[tt]=i;
		}
	for(i=1;i<num(n);i++)
		for(j=i+1;j<=num(n);j++)
			b[i][j]=b[i][j-1]|b[j][j];
	for(i=1;i<=m;i++){
		k=gi();ans&=tmp;int tsum=0;
		for(j=1;j<=k;j++){
			l=gi();r=gi();
			if(op&&i!=1){
				l^=pre;r^=pre;
				l=l%n+1;r=r%n+1;
				if(l>r)swap(l,r);
			}
			q[j].l=l,q[j].r=r;
		}
		sort(q+1,q+k+1,cmp);
		int _k=0,tmp=1;q[k+1].l=0x3f3f3f3f;
		for(j=1;j<=k;j++){
			if(q[tmp].r+1>=q[j+1].l)
				q[tmp].r=max(q[tmp].r,q[j+1].r);
			else{
				q[++_k]=q[tmp];
				tmp=j+1;
			}
		}
		k=_k;
		for(j=1;j<=k;j++){
			l=q[j].l;r=q[j].r;
			int _l,_r;
			tsum+=sum[r]-sum[l-1];
			if(num(r)-num(l)<2){
				
				_l=lower_bound(tong+1,tong+tt+1,l)-tong;
				_r=upper_bound(tong+1,tong+tt+1,r)-tong-1;
				for(o=_l;o<=_r;o++)
					ans.set(a[tong[o]]);
				/*
				for(o=l;o<=r;o++)
					if(a[o]>=0)ans.set(a[o]);
				*/
			}
			else{
				
				_l=lower_bound(tong+1,tong+tt+1,l)-tong;
				_r=upper_bound(tong+1,tong+tt+1,L*num(l))-tong-1;
				for(o=_l;o<=_r;o++)
					ans.set(a[tong[o]]);
				/*
				for(o=l;o<=L*num(l);o++)
					if(a[o]>=0)ans.set(a[o]);
				*/
				
				_l=lower_bound(tong+1,tong+tt+1,L*(num(r)-1)+1)-tong;
				_r=upper_bound(tong+1,tong+tt+1,r)-tong-1;
				for(o=_l;o<=_r;o++)
					ans.set(a[tong[o]]);
				/*
				for(o=L*(num(r)-1)+1;o<=r;o++)
					if(a[o]>=0)ans.set(a[o]);
				*/
				ans|=b[num(l)+1][num(r)-1];
			}
		}
		pre=ans.count()+tsum;
		printf("%d\n",pre);
	}
}

 

好像已经优化不了了

又想了一下,一个可以把二分去掉,进一步优化常数

但是要卡进1000ms还是非常困难啊。。。

 

本来整个卡常数过程不超过1.5h

结果因为电脑发生了种种问题,导致浪费了一个下午

“今天不知道为什么,电脑一直出问题,T3一开始用freopen的输入输出在本地测是0分,在lemon上是100分,后来为了卡常数,改了一下代码,在本地测没有RE,在lemon上就RE,当时以为是中病毒了。最后发现是O2的问题,有一个变量hcnt在O2的状态下被莫名其妙地赋成了一个极大值,就RE了。然后我在另一个地方开一个变量tt来代替hcnt,它就AC了。
还有,今天不知道为什么,我QQ登1min就会闪退,搜狗拼音输入法各种报错, 360杀毒杀出145个病毒,微信钉钉全部瘫痪。我现在换了妈妈的笔记本电脑,重装了GUIDE、Dev-C++、lemon才把错改完。。。估计电脑要重装系统了。。。”

 

最新版代码:(1200ms)

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<bitset>
using namespace std;
inline int gi()
{
	char c;int num=0,flg=1;
	while((c=getchar())<'0'||c>'9')if(c=='-')flg=-1;
	while(c>='0'&&c<='9'){num=num*10+c-48;c=getchar();}
	return num*flg;
}
#define N 100005
int L;
int a[N],tong[N],sum[N],hh[N],hcnt;
bitset<28000> b[36][36],tmp,ans;
inline int num(int x){return (x-1)/L+1;}
struct node{int l,r;}q[N];
bool cmp(node q,node w){return q.l<w.l||(q.l==w.l&&q.r<w.r);}
int main()
{
	freopen("interval.in","r",stdin);
	freopen("interval.out","w",stdout);
	int n,m,op,i,j,k,l,r,pre=0,o,tt=0;
	n=gi();m=gi();op=gi();
	L=n/35+1;
	for(i=1;i<=n;i++){a[i]=gi()-1;tong[a[i]]++;}
	for(i=1;i<=n;i++){
		if(tong[a[i]]>1) hh[++hcnt]=a[i];
		else sum[i]++;
		sum[i]+=sum[i-1];
	}
	sort(hh+1,hh+hcnt+1);
	hcnt=unique(hh+1,hh+hcnt+1)-hh-1;
	for(i=1;i<=n;i++)
		if(tong[a[i]]>1)b[num(i)][num(i)].set(a[i]=(lower_bound(hh+1,hh+hcnt+1,a[i])-hh-1));
		else a[i]=-1;
	memset(hh,0,sizeof(hh));tt=0;
	memset(tong,0,sizeof(tong));
	for(i=1;i<=n;i++)if(a[i]>=0){hh[++tt]=a[i];tong[i]=tt;}
	tong[n+1]=tt+1;
	for(i=n;i>=1;i--)if(!tong[i])tong[i]=tong[i+1];
	
	for(i=1;i<num(n);i++)
		for(j=i+1;j<=num(n);j++)
			b[i][j]=b[i][j-1]|b[j][j];
	for(i=1;i<=m;i++){
		k=gi();ans&=tmp;int tsum=0;
		for(j=1;j<=k;j++){
			l=gi();r=gi();
			if(op&&i!=1){
				l^=pre;r^=pre;
				l=l%n+1;r=r%n+1;
				if(l>r)swap(l,r);
			}
			q[j].l=l,q[j].r=r;
		}
		sort(q+1,q+k+1,cmp);
		int _k=0,tmp=1;q[k+1].l=0x3f3f3f3f;
		for(j=1;j<=k;j++){
			if(q[tmp].r+1>=q[j+1].l)
				q[tmp].r=max(q[tmp].r,q[j+1].r);
			else{q[++_k]=q[tmp];tmp=j+1;}
		}
		k=_k;
		for(j=1;j<=k;j++){
			l=q[j].l;r=q[j].r;
			int _l,_r;
			tsum+=sum[r]-sum[l-1];
			if(num(r)-num(l)<2){
				_l=tong[l];_r=tong[r+1]-1;
				for(o=_l;o<=_r;o++)ans.set(hh[o]);
			}
			else{
				_l=tong[l];_r=tong[L*num(l)+1]-1;
				for(o=_l;o<=_r;o++)ans.set(hh[o]);
				_l=tong[L*(num(r)-1)+1];_r=tong[r+1]-1;
				for(o=_l;o<=_r;o++)ans.set(hh[o]);
				ans|=b[num(l)+1][num(r)-1];
			}
		}
		pre=ans.count()+tsum;
		printf("%d\n",pre);
	}
}

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值