昨天看到一道好题,现与大家分享一下。
http://codeforces.com/contest/165/problem/E
题目大意:给一个序列a1~an(n<=1000000),问对于每一个ai来说,是否存在一个数x使得 ai&x==0且x是序列a 中的元素。ai的范围是(1<=ai<=4000000)。
思路:对于一个数,比如说53,其二进制表示为 110101,取它的反,即为001010,我们知道,若一个数x使得x&53==0,则x的二进制一定是这样一个形式:00?0?0(其中?表示可以为0也可以为1)。那么我们设这样一个数组 vis[x]={0,1},表示x是否可以由序列a中的元素,将其二进制中的若干0改为1后得到。比如说如果a中存在53(110101),那么我们可以得到 (111101)61,(110111)55,(111111)63,则vis[53]=vis[55]=vis[61]=vis[63]=1,这个数列我们可以先预处理一遍得到,因为ai最大为4000000,所以预处理的复杂度为 (1<<K)*K(其中K为序列中最大的数其二进制表示的长度,最大为22)。那么有了这个数组,我们就可以求出对于每一个数的答案了,首先,我们要求ai对应的数,我们首先取反,得到tmp,此时若vis[tmp]==0,我们可以肯定序列a中不存在与ai与操作后等于0的数,直接输出-1,否则,我们可以知道一定存在一个数,那么我们怎么求这个数呢?
具体步骤如下:
我们首先设答案ans为0,我们枚举tmp的每一位,如果遇到1,则我们把这一位设为0后,再看tmp是否存在,如果不存在,说明我们要找的那个数这一位上一定为1,则我们将ans的这一位赋值为1,然后将tmp恢复后继续遍历,如果存在,说明我们要找的数这一位可以为0(注意不是一定),则我们继续遍历即可。如果遇到0,直接继续遍历,因为我们要找的数这一位上一定为0。遍历完之后,ans的值即为答案,输出即可。这一步的时间复杂度为O(n*K),(K由上面定义),可以在规定的时间内跑完。具体代码如下:
#include <iostream> #include <string.h> #include <stdio.h> #include <algorithm> #define ll long long #define maxn 4500000 using namespace std; int getlen(int x) { int sum=0; while(x) { sum++; x/=2; } return sum; } int vis[maxn]; int a[1000010]; int main() { //freopen("dd.txt","r",stdin); memset(vis,0,sizeof(vis)); int n,max=0,i,j; scanf("%d",&n); for(i=1;i<=n;i++) { scanf("%d",&a[i]); if(max<a[i]) max=a[i]; vis[a[i]]=1; } if(n==1) { printf("-1\n"); return 0; } int len=getlen(max); int limit=(1<<len)-1; for(i=0;i<=limit;i++) { if(!vis[i]) continue; for(j=0;j<len;j++) { if(((1<<j)&i)==0) { vis[i^(1<<j)]=1; } } } for(i=1;i<=n;i++) { int tmp=limit-a[i]; //printf("%d\n",tmp); if(!vis[tmp]) printf("-1 "); else { int ans=0; for(j=0;j<len;j++) { if(tmp&(1<<j)) { if(vis[tmp^(1<<j)]) tmp^=(1<<j); else { ans+=1<<j; } } } printf("%d ",ans); } } printf("\n"); return 0; }