uoj #91. 【集训队互测2015】最大异或和 / bzoj3984: 最大异或和

题意

我有一个数列a[1],a[2],…,a[n],每个a[i]是小于2^m的非负整数。
现在请您实现三种操作,格式说明如下:
1 x y w 对于所有x<=i<=y,将a[i]修改为a[i] xor w;
2 x y w 对于所有x<=i<=y,将a[i]修改为w;
3 从a[1],a[2],…,a[n]中选出若干个数,使它们的xor和最大,并输出这个最大值。
(xor表示按位异或运算)

题解

最近写代码SB错误越来越多了,都要写好几个小时才调完。。
但我的方法确实不优秀。。

先说一个一定要做的转化,就是把序列差分就变成单点修改了
就是你把每个数变成从 [ i , j ] [i,j] [i,j]这些时刻出现
这个怎么处理自己思考吧
容易发现对于区间赋值后一堆0你不用管,因此有用的数是 n + m n+m n+m个的

然后后面就有两个做法了
一个是我的超级SB做法。。另外一个是优秀的标解
其实标解的思路我以前见过,但是忘了

先说说我的吧
你可以把区间排序按l端点排序
然后对于每一个位开一个vector
存储他不同时间段时发挥作用的数字是什么
容易发现,每一个数不会被分成超过n段,因此总和是 ( n + m ) n (n+m)n (n+m)n
然后做的时候,你可以还原线性基,就是你找到线性基这一位在这个时间段是哪一个就可以了
以上过程都可以用bitset优化,时空复杂度都是 m 3 / 32 m^3/32 m3/32
空间巨大
似乎除了可以动态询问前i个答案没什么优点
显然,这个前i个答案可以预处理出来。。
于是就毫无优点了,唯一的优点就是我自己想的。。

第二个做法就简单很多:
区间赋值就相当于删除一些数,修改两个数,修改=删除+插入,瓶颈在于怎么在线性基中删一个数,维护每个数在序列出现的时间区间[li,ri],按时间轴扫过去,在线性基加入一个数时,从高位往低位,若现在可以插入第w位,第w位无值直接插入,有值假设这个值j的区间是[lj,rj],若rj<ri就把j取出来,i插入,把取出来的数接着往下插,这样删除一个数时可以直接从线性基中删掉他,基中没有其他数可以替代他的位置
似乎见过这个思路,但是很久没用过就忘了。。一定要记下来啊。。

附上代码吧(当然是第一种的)
虽然说起来很简单,但细节较多还是调了很久

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<bitset>
#include<vector>
using namespace std;
const int N=2001;
int n,m,q;
char ss[N];
bitset<N> s[N];
bitset<N> a[N];
bitset<N> g;//临时变量 
int last[N];//上一个时间点是什么 
struct qq
{
	int l,r;
	bitset<N> h;
}t[N*5];int tot=0;
qq l[N*N/5];
bool ask[N];
bool cmp (qq x,qq y)	{return x.l==y.l?x.r<y.r:x.l<y.l;}
vector<int> vec[N];
int id=0;
int now[N];
void prepare ()
{
	for (int u=1;u<=tot;u++)
	{
		for (int i=m-1;i>=0;i--)
		{
			if (t[u].l>t[u].r) break;
			if (t[u].h[i]==1)//这一位有1
			{
				int siz=vec[i].size();
				if (siz==0)
				{
		
					id++;
					l[id]=t[u];
					t[u].r=t[u].l-1;
					vec[i].push_back(id);
				}
				else
				{
					int R=l[vec[i][siz-1]].r+1;
					if (R<=t[u].r)
					{
						
						id++;
						l[id]=t[u];
						l[id].l=max(l[id].l,R);
						t[u].r=min(t[u].r,l[id].l-1);

						vec[i].push_back(id);
					}
					t[u].r=min(t[u].r,R-1);
					t[u].h=t[u].h^l[vec[i][siz-1]].h;
				}
			}
		}
	}
}
void solve ()
{
	for (int u=0;u<m;u++) now[u]=0;
	for (int u=1;u<=q;u++)
		if (ask[u])//这个是要询问的 
		{
			g.reset();
			for (int i=m-1;i>=0;i--)
			{
				int siz=vec[i].size();
				while (now[i]<siz)
				{
					if (l[vec[i][now[i]]].r<u) now[i]++;
					else break;
				}
				if (now[i]>=siz) continue;
				if (l[vec[i][now[i]]].l>u) continue;
	
				if (g[i]==0)	
				{
					g=g^l[vec[i][now[i]]].h;
				}
			}
			for (int i=0;i<m;i++) 
			{
				if (g[m-i-1]) printf("1");
				else printf("0");
			}
			printf("\n");
		}
}
int main()
{
	memset(ask,false,sizeof(ask));
	scanf("%d%d%d",&n,&m,&q);
	for (int u=1;u<=n;u++)
	{
		scanf("%s",ss);
		for (int i=0;i<m;i++) s[u][m-i-1]=(ss[i]-'0');
		a[u]=s[u];
	}
	for (int u=n;u>=2;u--) s[u]=s[u]^s[u-1];
	for (int u=1;u<=n;u++) last[u]=1;
	for (int u=1;u<=q;u++)
	{
		int op;
		scanf("%d",&op);
		if (op==1)
		{
			int x,y;
			scanf("%d%d",&x,&y);
			scanf("%s",ss);
			for (int i=0;i<m;i++) g[m-i-1]=(ss[i]-'0');
			if (last[x]!=-1)	{tot++;t[tot].l=last[x];t[tot].r=u-1;t[tot].h=s[x];}
			last[x]=u+1;s[x]=s[x]^g;
			y++;
			if (y<=n)
			{
				if (last[y]!=-1)	{tot++;t[tot].l=last[y];t[tot].r=u-1;t[tot].h=s[y];}
				last[y]=u+1;s[y]=s[y]^g;
			}
			for (int i=x;i<y;i++) a[i]=a[i]^g;
		}
		if (op==2)
		{
			int x,y;
			scanf("%d%d",&x,&y);
			scanf("%s",ss);
			for (int i=0;i<m;i++) g[m-i-1]=(ss[i]-'0');
			if (last[x]!=-1)	{tot++;t[tot].l=last[x];t[tot].r=u-1;t[tot].h=s[x];last[x]=u;}
			last[x]=u+1;s[x]=g^a[x-1];
			y++;
			if (y<=n)
			{
				if (last[y]!=-1)	{tot++;t[tot].l=last[y];t[tot].r=u-1;t[tot].h=s[y];}
	
				last[y]=u+1;s[y]=g^a[y];
			}
			for (int i=x;i<y;i++) a[i]=g;
			for (int i=x+1;i<y;i++) 
			{
				if (last[i]!=-1)
				{
					tot++;
					t[tot].l=last[i];
					t[tot].r=u-1;
					t[tot].h=s[i];
					s[i].reset();
				}
				last[i]=-1;
			}
		}
		if (op==3)	ask[u]=true;
	}
		
	for (int u=1;u<=n;u++)
		if (last[u]!=-1)
			{
				tot++;t[tot].l=last[u];t[tot].r=q;
				t[tot].h=s[u];
			}
	sort(t+1,t+1+tot,cmp);
	prepare();
	solve();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值