题目大意
求出每个数与其左边的数$and/or/xor$的最大值,以及达到最大值的个数。
题目分析
又是一道神题。
考虑有两种暴力方式:
- 对于每次询问的时候查找其前面与其运算最大的值。
- 对于每次插入新值时预处理对每个可能出现的数的贡献。
两种方法都是$O(n^2)$或$O(nV)$的,其中$V$是值域大小。
我们考虑对值域进行分块,$V\le2^{16}$,我们将其分为前$8$位与后$8$位。
下文用$A$表示已经加入集合的数,$B$表示当前即将加入集合的数。
设置状态$f[l,r]$,其中$l$表示$A$的前$8$位,$r$表示$B$的后$8$位,$f[l,r]$表示最大值。
在更新答案的时候,枚举所有的$l$,查询贡献。(第一种暴力)
在加入集合的时候,枚举所有的$r$,处理其贡献。(第二种暴力)
这样我们就可以在$O(n\sqrt V)$的时间内解决问题。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| #include<algorithm> #include<iostream> #include<iomanip> #include<cstring> #include<cstdlib> #include<climits> #include<vector> #include<cstdio> #include<cmath> #include<queue> using namespace std;
inline const int Get_Int() { int num=0,bj=1; char x=getchar(); while(x<'0'||x>'9') { if(x=='-')bj=-1; x=getchar(); } while(x>='0'&&x<='9') { num=num*10+x-'0'; x=getchar(); } return num*bj; }
const int maxn=100005,maxb=256;
int n,type,f[maxn][maxb],g[maxn][maxb]; char opt[5];
int Cal(int x,int y) { if(opt[0]=='a')return x&y; if(opt[0]=='o')return x|y; return x^y; }
int main() { n=Get_Int(); scanf("%s",opt); type=Get_Int(); for(int i=1; i<=n; i++) { int v=Get_Int(); int l=(v>>8),r=v^(l<<8),Max=-INT_MAX,sum=0; for(int j=0; j<maxb; j++) if(g[j][r]) { int now=(Cal(l,j)<<8)|f[j][r]; if(now>Max) { Max=now; sum=g[j][r]; } else if(now==Max)sum+=g[j][r]; } for(int j=0; j<maxb; j++) { int now=Cal(j,r); if(!g[l][j]||now>f[l][j]) { f[l][j]=now; g[l][j]=1; } else if(now==f[l][j])g[l][j]++; } if(i==1)continue; if(!type)printf("%d\n",Max); else printf("%d %d\n",Max,sum); } return 0; }
|