题意:
给定长度为n的序列a
计算max(a[i]|(a[j]&a[k])),其中i<j<k
数据范围:n<=1e6,a(i)<=2e6
解法:
考虑枚举a[i],找最大的a[j]&a[k]
1.做法1:
因为要保证i<j<k,所以倒着插入,因为我们要找的是用来与|的,
所以Sosdp记录每个mask的超集信息,那么插入x则是往x的子集插,
注意:在剪枝条件下,插入函数别写错了,如果子集插少了就wa了.
插入函数是"递归"实现的,具体看代码.
查询的时候贪心第从高位到低位判断能否取到,
如果超集数量>=2则能,否则不能
2.做法2:
超集Sosdp,记录每种状态超集的最大的两个下标,
枚举a[i]查询的时候,判断是否超集的两个下标都大于当前i就行了
总结:
递归插入是要插入所有子集的,
在我的想法里,理论上二进制位有一个1,子集数量就*2,那么子集大小不是指数级的了吗??
多插几个数就凉了,所以这题要有剪枝,d[x]>=2的时候就退出,保证每个位置不被插超过两次.
这么看来还是第二种做法比较举有适应性.
参考:
1.做法1
https://blog.csdn.net/weixin_45564571/article/details/100130070
2.做法2
https://www.cnblogs.com/GavinZheng/p/11534007.html
code1:
#include<bits/stdc++.h>
using namespace std;
const int maxm=4e6+5;
int d[maxm];
int a[maxm];
int n;
void add(int x,int k){//按位插入
if(d[x]>=2)return ;//不剪枝会tle
if(k==-1){
d[x]++;//到最后一层的时候d[x]++;
return ;
}
if(x>>k&1){//如果是1则可以删1
add(x,k-1);//不删
add(x^(1<<k),k-1);//删
}else{//如果是0则不能删
add(x,k-1);//只能不删
}
}
int ask(int x){
int ans=0;
for(int i=21;i>=0;i--){//贪心地从高位选
if(!(x>>i&1)&&d[ans|(1<<i)]>=2){
ans|=(1<<i);
}
}
return x|ans;
}
signed main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
int ans=0;
for(int i=n;i>=1;i--){
if(i<=n-2){
ans=max(ans,ask(a[i]));
}
add(a[i],21);
}
printf("%d\n",ans);
return 0;
}
code2:
#include<bits/stdc++.h>
using namespace std;
const int maxm=4e6+5;
struct Node{
int a,b;//最大和次大
}d[maxm];
int a[maxm];
int n;
void add(int x,int id){
if(id>d[x].a){
d[x].b=d[x].a;
d[x].a=id;
}else if(id>d[x].b){
d[x].b=id;
}
}
void init(){
for(int i=1;i<=n;i++){
add(a[i],i);
}
for(int i=0;i<22;i++){
for(int j=(1<<21);j>=0;j--){
if(j>>i&1){//更新子集
add(j^(1<<i),d[j].a);
add(j^(1<<i),d[j].b);
}
}
}
}
int ask(int x,int id){
int ans=0;
for(int i=21;i>=0;i--){
if(!(x>>i&1)&&d[ans^(1<<i)].b>id){
ans|=(1<<i);
}
}
return x|ans;
}
signed main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
init();
int ans=0;
for(int i=1;i<=n-2;i++){
ans=max(ans,ask(a[i],i));
}
printf("%d\n",ans);
return 0;
}