题目
长度为n(3<=n<=1e6)的数组a[],0<=ai<=2e6
求最大的ai|(aj&ak)的值,满足三元组i<j<k
思路来源
CodeForces -1208F Bits And Pieces(位运算,贪心,SoS DP)_1208c codeforces-CSDN博客
题解
考虑可以枚举一维ai,
问题转化为,在ai没有的那些二进制位里,从高位到低位选,
假设当前贪心选取的二进制位为bit,bit对应的任意超集里,是否存在两个位置j,k比i大
由于只需要考虑两个位置,故维护pair dp[i]为i对应的二进制状态的最大的两个位置
由于对于bit要考虑其超集,故令超集对其子集更新,
全下放的复杂度不能接受,考虑每次只舍一个二进制位,倒序下放
复杂度O(n+m*2^m),其中m为二进制位20
O(m*2^m)预处理,然后枚举一维ai,
从高位到低位贪心选位,然后考虑这个额外选的二进制状态里的最大两个位置j,k,
如果均大于就或上这一位,否则就保留着之前的状态,继续往低位选
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e6+10,M=2e6+10;
#define fi first
#define se second
typedef pair<int,int> P;
int n,v,ans,a[N];
P dp[M];
//dp[i}:表示i的超集(即子集中包含i)的最大位置和次大位置
void merge(P &a,int x){
if(x>a.fi){
a.se=a.fi;
a.fi=x;
}
else if(x>a.se && x!=a.fi){
a.se=x;
}
}
int main(){
scanf("%d",&n);
for(int i=0;i<n;++i){
scanf("%d",&a[i]);
merge(dp[a[i]],i);
}
for(int i=20;i>=0;--i){
for(int j=M-1;j>=0;--j){
if(j>>i&1){//去掉1位的子集 每个向相邻的子集下放答案
merge(dp[j^(1<<i)],dp[j].fi);
merge(dp[j^(1<<i)],dp[j].se);
}
}
}
for(int i=0;i<n-2;++i){//i,n-2,n-1
int s=0;
for(int j=20;j>=0;--j){
if(!(a[i]>>j&1) && dp[s|(1<<j)].fi>i && dp[s|(1<<j)].se>i){
s|=(1<<j);
}
}
ans=max(ans,a[i]|s);
}
printf("%d\n",ans);
return 0;
}
题解2(2024.6.25补充)
有个群友问他的均摊做法为啥TLE,然后看了下代码,常数太大了,魔改了下就过了
由于每个状态最多被摊两次,直接下放均摊所有子状态即可,
每次只删一个1下放,这样多访问一层也最多会访问m个而已,
大概总访问状态数是2*m*2^m级别的,严格的次数不太会证
query[x]记录x这个状态第一次被更新是被哪个位置更新的
// LUOGU_RID: 163019403
#pragma GCC optimize(2)
#include <algorithm>
#include <cstdio>
#include <vector>
#include <set>
namespace Input{
const int BUFFLEN = 1 << 20;
char buf[BUFFLEN | 2], *p1 = buf, *p2 = buf;
inline char getc(){
if(p1 == p2)
p1 = buf,
p2 = buf + fread(buf, 1, BUFFLEN, stdin);
return p1 == p2? EOF: *p1++;
}
inline bool isdgt(const char& op){return op >= '0' && op <= '9';}
inline bool isalpha(const char& op){return op >= 'a' && op <= 'z';}
inline bool iscapitcal(const char& op){return op >= 'A' && op <= 'Z';}
inline bool isletter(const char& op){return isalpha(op) || iscapitcal(op);}
inline bool ischr(const char& op){return isletter(op);}
long long read(){
static long long res; static char op, f;
for(f = 0, op = getc(); !isdgt(op); op = getc()) f |= (op == '-');
for(res = 0; isdgt(op); op = getc()) res = res * 10 + (op ^ '0');
return f? -res: res;
}
int readstr(char *s){
static int len; static char op;
do op = getc(); while(!ischr(op));
for(len = 0; ischr(op); op = getc()) s[len++] = op;
return len;
}
}
using namespace std;
using Input::read;
typedef long long ll;
const int N = 1 << 21;
int query[N];
bool tax1[N], tax2[N];
void add2(const int& x){
if(tax2[x]) return;
tax2[x] = 1;
for(int t = x; t; t ^= t & -t)
add2(x ^ (t & -t));
}
void add1(const int& x,int y){
if(tax1[x]){
if(query[x]==y)return;
if(!tax2[x]) add2(x);
return;
}
else if(query[x]<y){
query[x]=y;
tax1[x]=1;
for(int t = x; t; t ^= t & -t)
add1(x ^ (t & -t),y);
}
}
void addval(const int& x,int y){
add1(x,y);
}
int a[N], n;
signed main(int argc, char **argv){
// freopen("order.in", "r", stdin);
// freopen("F_tax.out", "w", stdout);
n = read();
for(int i = 1; i <= n; ++i)
a[i] = read();
addval(a[n],n); // puts("a");
addval(a[n - 1],n-1); // puts("b");
int ans = 0;
for(int i = n - 2; i; --i){
int tmp = 0;
for(int j = 20; ~j; --j)
if(!(a[i] & (1 << j))){
if(tax2[tmp | (1 << j)]) tmp |= (1 << j);
}
ans = std::max(ans, a[i] | tmp);
addval(a[i],i);
}
printf("%d\n", ans);
return 0;
}