[JZOJ4616] 【NOI2016模拟7.12】二进制的世界

题目

题目大意

给你一个数列,每个数为 [ 0 , 65535 ] [0,65535] [0,65535]内的整数。
给定一个位运算操作 o p t opt opt,是 a n d and and o r or or x o r xor xor中的一种。
求每个数左边与这个数进行操作的最大值,以及达到最大值的数量。


思考历程

见到这东西,首先想到的当然是Trie了!
显然,把Trie建出来之后,搞定 x o r xor xor是分分钟的事情。
然而 a n d and and o r or or该怎么搞?
下面用 a n d and and来举例:
从高位往低位做,如果当前位是 1 1 1就好说,如果是 0 0 0,那两边都可以走。
首先想到的是先走一边,再走另一边,可是这显然会爆炸……
然后想到的是BFS下去……但是这样依然很慢,因为有可能一直做到最下面才被删掉……
我想到了某天做的一道题目(好像叫做量子纠缠?),开始思考是否可以用并查集的方式来做。
一开始感觉好像可以,用个标记什么的,如果不做就不合并喽~
然后发现打标记不现实,必须要递归下去合并……
但是询问操作有很多个啊……这意味着做完之后还要再恢复到原样,然后再做……
于是时间复杂度就不能保证了:如果数据里有一堆 0 0 0,那是不是每次都要把每个子树合并一遍?
所以还是不行……
接着我意识到了不能局限于Trie,可能还有别的做法。
然后我就想到了分块!
可以将前 8 8 8位和后 8 8 8位拆开,分别搞。
我的做法是将前面 8 8 8位建Trie。怎么建?我建了 2 8 2^8 28个不同的Trie。
这些Trie为忽略一些位建立的(当某个位是 0 0 0的时候,选什么都没有关系,所以这一位被忽略了)。
然后我在这些Trie的每个叶子节点上都开了一个表,表示后 8 8 8位是多少时的数量。
就这样,我就打出一个时间复杂度为 O ( 8 ∗ 2 8 n ) O(8*2^8n) O(828n)的做法,自我感觉良好,好像可以压线过去……
交上去,爆0……后来找到错误,再次交上去,时间超限40……
开了个 O 3 O3 O3就有100……


正解

正解的时间复杂度要少个 8 8 8。这个“常数”决定了40分和100分的差距。
f i , j f_{i,j} fi,j表示之前的数的前 8 8 8位为 i i i,它的后 8 8 8位和后 8 8 8位为 j j j的数进行操作的最大值以及对应的数量。
对于每个数 x x x,显然可以拆成 x = 2 8 a + b x=2^8a+b x=28a+b的形式。
询问的时候,枚举 i i i,用 f i , b f_{i,b} fi,b来更新答案。
修改的时候,枚举 j j j,用 b   o p t   j b \ opt\ j b opt j来更新 f a , j f_{a,j} fa,j
程序十分简短,特别好打。


代码

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
int n,type;
char str[5];
int XOR(int a,int b){return a^b;}
int AND(int a,int b){return a&b;}
int OR(int a,int b){return a|b;}
struct Answer{
	int val,num;
	inline void update(int _val){
		if (_val>val)
			val=_val,num=1;
		else if (_val==val)
			num++;
	}
}f[256][256];
inline void solve(int opt(int,int)){//懒得打太多,于是干脆将参数设为函数……
	for (int i=1;i<=n;++i){
		int x,a,b;
		scanf("%d",&x);
		a=x>>8,b=x&255;
		if (i!=1){
			int mx=-1,num=0;
			for (int j=0;j<256;++j)
				if (f[j][b].num){
					int tmp=opt(j,a)<<8|f[j][b].val;
					if (tmp>mx)
						mx=tmp,num=f[j][b].num;
					else if (tmp==mx)
						num+=f[j][b].num;
				}
			if (type)
				printf("%d %d\n",mx,num);
			else 
				printf("%d\n",mx);
		}
		for (int j=0;j<256;++j)
			f[a][j].update(opt(j,b));
	}
}
int main(){
	freopen("binary.in","r",stdin);
	freopen("binary.out","w",stdout);
	scanf("%d%s%d",&n,str,&type);
	if (*str=='x')
		solve(XOR);
	else if (*str=='a')
		solve(AND);
	else
		solve(OR);	
	return 0;
}

也把我的那个方法贴上来,开了O3……

#pragma GCC optimize("O3")
#pragma G++ optimize("O3")
using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
int n,type;
char opt[5];
struct Node{
	Node *c[2];
} d[14001];
int tor[14001];
int cnt;
Node *root[256];
int rest[14001][256],nr;
inline void insert(int r,int x){
	Node *t=root[r];
	for (int i=15;i>=8;--i)
		if (r>>i-8&1){
			if (!t->c[x>>i&1])
				t->c[x>>i&1]=&d[++cnt];
			t=t->c[x>>i&1];
		}
	if (!tor[t-d])
		tor[t-d]=++nr;
	rest[tor[t-d]][x&255]++;
}
int time1,time2;
int main(){
	freopen("binary.in","r",stdin);
	freopen("binary.out","w",stdout);
	scanf("%d%s%d",&n,opt,&type);
	for (int i=0;i<256;++i)
		root[i]=&d[++cnt];
	if (*opt=='x'){
		for (int i=1;i<=n;++i){
			int x;
			scanf("%d",&x);
			if (i!=1){
				Node *t=root[255];
				int ans=0;
				for (int j=15;j>=8;--j)
					if (t->c[(x>>j&1)^1]){
						t=t->c[(x>>j&1)^1];
						ans|=1<<j;
					}
					else
						t=t->c[x>>j&1];
				int *rst=rest[tor[t-d]],mx=-1,y=x&255;
				for (int j=0;j<256;++j)
					if (rst[j] && (mx==-1 || (y^j)>mx))
						mx=y^j;
				printf("%d %d\n",ans|mx,rst[y^mx]);
			}
			insert(255,x);
		}
		return 0;	
	}
	for (int i=1;i<=n;++i){
		int x;
		scanf("%d",&x);
		if (i!=1){
			int mode=0;
			Node *t;
			if (*opt=='a')
				t=root[mode=x>>8];
			else
				t=root[mode=x>>8^255];
			int ans=0;
			for (int j=15;j>=8;--j)
				if (mode>>j-8&1){
					if (t->c[1]){
						t=t->c[1];
						ans|=1<<j;
					}
					else
						t=t->c[0];
				}
				else
					ans|=x&1<<j;
			int *rst=rest[tor[t-d]],y=x&255,mx=-1,ans2=0;
			if (*opt=='a'){
				for (int j=0;j<256;++j)
					if (rst[j]){
						if (mx==-1 || (y&j)>mx)
							mx=y&j,ans2=rst[j];
						else if ((y&j)==mx)
							ans2+=rst[j];
					}
				if (type==1)
					printf("%d %d\n",ans|mx,ans2);
				else
					printf("%d\n",ans|mx);
			}
			else{
				for (int j=0;j<256;++j)
					if (rst[j]){
						if (mx==-1 || (y|j)>mx)
							mx=y|j,ans2=rst[j];
						else if ((y|j)==mx)
							ans2+=rst[j];
					}
				if (type==1)
					printf("%d %d\n",ans|mx,ans2);
				else
					printf("%d\n",ans|mx);
			}
		}
		for (int j=0;j<256;++j)
			insert(j,x);
	}
	return 0;
}

这码量的差距啊……

总结

对于位运算,我们的思想不能再局限于Trie了……
分块还是挺有用的啊……

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值