zoukankan      html  css  js  c++  java
  • [JZOJ4616] 【NOI2016模拟7.12】二进制的世界

    题目

    题目大意

    给你一个数列,每个数为[0,65535][0,65535]内的整数。
    给定一个位运算操作optopt,是andandororxorxor中的一种。
    求每个数左边与这个数进行操作的最大值,以及达到最大值的数量。


    思考历程

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


    正解

    正解的时间复杂度要少个88。这个“常数”决定了40分和100分的差距。
    fi,jf_{i,j}表示之前的数的前88位为ii,它的后88位和后88位为jj的数进行操作的最大值以及对应的数量。
    对于每个数xx,显然可以拆成x=28a+bx=2^8a+b的形式。
    询问的时候,枚举ii,用fi,bf_{i,b}来更新答案。
    修改的时候,枚举jj,用b opt jb opt j来更新fa,jf_{a,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
    ",mx,num);
    			else 
    				printf("%d
    ",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
    ",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
    ",ans|mx,ans2);
    				else
    					printf("%d
    ",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
    ",ans|mx,ans2);
    				else
    					printf("%d
    ",ans|mx);
    			}
    		}
    		for (int j=0;j<256;++j)
    			insert(j,x);
    	}
    	return 0;
    }
    

    这码量的差距啊……

    总结

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

  • 相关阅读:
    C++字节对齐与位域
    使用GDB调试将符号表与程序分离后的可执行文件
    在windows上编译apr库
    使用samba共享文件夹,提供给window访问
    Linux常用命令
    使用VS2015编译xlslib库
    VS资源收藏<持续更新中>
    使用Visual Studio 2017 C++17模块(module)特性
    RMAN中format的参数
    C#的Process类的一些用法
  • 原文地址:https://www.cnblogs.com/jz-597/p/11145211.html
Copyright © 2011-2022 走看看