省选模拟赛第三场 T1 与非(巧妙的线段树+后缀insert优化 || 思维题)

 

 

 

 

 

 

题解

先来讲一下我的做法

 

于是在考试的时候就爆0了。。。

但是O(logn)的insert太慢了,我们又考虑到题目只从尾端insert

于是我们就可以从下向上合并pushup

当它是父亲节点的右儿子是就可以把它的值上传了(因为这时它的父亲就有可能对答案做出贡献了)

如果是左儿子就停止上传

然后我们就愉快地发现这样做是均摊O(1)的

就过了。。。

这道题告诉我们:

1、在前驱值范围较小、运算有不具有结合律的时候是可以用线段树维护多个标记来解决的

2、尾端insert可以通过只在右儿子上传的信息的方法优化到均摊O(1)的

 

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
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 4000005
#define lc i<<1
#define rc i<<1|1
struct node{
	int l,r;bool v[2],s[2];
}a[N<<2];
bool val[N];int num[N];
inline void pushup(int i)
{
	a[i].v[1]=a[rc].v[a[lc].v[1]];
	a[i].v[0]=a[rc].v[a[lc].v[0]];
	a[i].s[1]=a[lc].s[1]^a[rc].s[a[lc].v[1]];
	a[i].s[0]=a[lc].s[0]^a[rc].s[a[lc].v[0]];
}
void build(int i,int l,int r)
{
	a[i].l=l;a[i].r=r;
	if(l==r){num[l]=i;return;}
	int mid=(l+r)>>1;
	build(lc,l,mid);build(rc,mid+1,r);
}
int q,ans;
void query(int i,int l,int r)
{
	if(a[i].l>r||a[i].r<l)return;
	if(l<=a[i].l&&a[i].r<=r){
		ans^=a[i].s[q];
		q=a[i].v[q];
		return;
	}
	query(lc,l,r);query(rc,l,r);
}
int main()
{
	freopen("nand.in","r",stdin);
	freopen("nand.out","w",stdout);
	int n,m=0,i,op,l,r,x;
	n=gi();build(1,1,n);
	for(i=1;i<=n;i++){
		op=gi();
		if(op==1){
			x=gi();
			x^=ans;
			val[++m]=x;
			int tmp=num[m];
			a[tmp].v[1]=a[tmp].s[1]=!(1&x);
			a[tmp].v[0]=a[tmp].s[0]=1;
			while((tmp>>1)&&(tmp&1)){
				tmp>>=1;
				pushup(tmp);
			}
		}
		else{
			l=gi();r=gi();
			if(ans)l=m-l+1,r=m-r+1,swap(l,r);
			ans=q=val[l];query(1,l+1,r);
			printf("%d\n",ans);
		}
	}
}

 

还有,尽量节约内存开销。。。防止MLE

 

 

其实还有一种做法(by Master.Yi)

代码:

#include<bits/stdc++.h>
#define maxn 4000005
using namespace std;
char cb[1<<18],*cs,*ct;
#define getc() (cs==ct&&(ct=(cs=cb)+fread(cb,1,1<<18,stdin),cs==ct)?0:*cs++)
inline void read(int &a){
	char c;while(!isdigit(c=getc()));
	for(a=c-'0';isdigit(c=getc());a=a*10+c-'0');
}
//n: number of 0, len: total length.
int m,n,len,s[maxn],a[maxn],ans;
int main()
{
	freopen("nand.in","r",stdin);
	freopen("nand.out","w",stdout);
	a[n=1]=0,a[n+1]=1e9;
	read(m);int op,x,l,r,L,R;
	while(m--){
		read(op),read(x);
		if(op==1){
			x^=ans,len++;
			if(x) s[n]=s[n-1]^((len-a[n])%4<=1);
			else a[++n]=len,s[n]=s[n-1]^1,a[n+1]=1e9;
		}
		else{
			l=x,read(r); if(ans) l=len-l+1,r=len-r+1,swap(l,r);
			L=upper_bound(a+1,a+1+n,l)-a,R=upper_bound(a+1,a+1+n,r)-a-1;
			if(a[L]>r&&a[R]<l) {printf("%d\n",ans=(r-l)%4<=1);continue;}
			if(a[L]>r&&a[R]==l) {printf("%d\n",ans=(r-l+3)%4<=1);continue;}
			ans=0;
			if(L<R) ans=s[R-1]^s[L-1];
			ans^=(a[L]-l-(a[L-1]==l)+3)%4<=1;
			ans^=(r-a[R])%4<=1;
			printf("%d\n",ans);
		}
	}
}

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值