题目大意:
长度为 n ( ≤ 1 e 6 ) n(\le 1e6) n(≤1e6)的序列 a a a,找到最大的 a i ∣ ( a j & a k ) a_i|(a_j\&a_k) ai∣(aj&ak), i < j < k i<j<k i<j<k
解题思路:
- 对于位运算球最大值的题目,一般都会贪心从高位向低位填充的方法
- 先设可挑选的集合为: S 0 , 1 , 2 , . . . S_{0,1,2,...} S0,1,2,...
- 从后往前枚举 a i a_i ai,然后从最高位贪心枚举,如果枚举到 a i a_i ai的第 p p p位为1,那么 a j , a k a_j,a_k aj,ak从 S p S_p Sp取任意都行,且 S p − 1 = S p S_{p-1}=S_{p} Sp−1=Sp, r e s res res更新
- 如果第 p p p位为0,那么判断 S p S_p Sp中是否有至少两个数第 p p p位为1,将这些数放到 S p − 1 S_{p-1} Sp−1中,答案会更新 r e s ∣ = 1 < < p res |= 1<<p res∣=1<<p,否则就 S p − 1 = S p S_{p-1}=S_p Sp−1=Sp, r e s res res不更新
- 但是如果按照上面那样更新 S S S,会超时,所以要重新定义 S S S, S ( 1100 ) 2 S_{(1100)_2} S(1100)2代表第3位有1,且第2位有1的集合,那 S ( 1100 ) 2 S_{(1100)_2} S(1100)2不就是从 S x S_{x} Sx, x & ( 1100 ) 2 = x x \& (1100)_2=x x&(1100)2=x,递推过来的下标集合嘛
- 这就是经典的 S O S d p SOSdp SOSdp, S O S d p SOSdp SOSdp求的是 ∑ m a s k & i = i B [ i ] \sum_{mask\&i=i}B[i] ∑mask&i=iB[i]
- 而本题求的是: ∑ m a s k & i = m a s k B [ i ] \sum_{mask\&i=mask}B[i] ∑mask&i=maskB[i],状态转移反过来就好
- d p [ i ] [ j ] dp[i][j] dp[i][j]代表的是有多少个数可以将前 j j j位的某些位置1变为0,从而变为 i i i
AC代码:
#include <bits/stdc++.h>
#define ft first
#define sd second
#define pb push_back
#define IOS ios::sync_with_stdio(false), cin.tie(0), cout.tie(0) //不能跟puts混用
#define seteps(N) fixed << setprecision(N)
#define endl "\n"
const int maxn = 1e6 + 10;
using namespace std;
typedef long long ll;
typedef double db;
typedef pair<int, int> pii;
const ll mod = 1e9 + 7;
int n, a[maxn];
int dp[1 << (21)][22];
void update(int res, int num) {
if (num > 20) return;
if (dp[res][num] > 1) return;
dp[res][num]++;
update(res, num + 1);
if ((res >> num) & 1) update(res ^ (1 << num), num);
}
int main() {
IOS;
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
int ans = 0;
for (int i = n; i >= 1; i--) {
int res = 0, sta = 0;
for (int j = 20; j >= 0; j--) {
if ((a[i] >> j) & 1) res |= (1 << j);
else if (dp[sta | (1 << j)][20] > 1) res |= (1 << j), sta |= (1 << j);
}
update(a[i], 0);
if (i <= n - 2) ans = max(ans, res);
}
cout << ans << endl;
return 0;
}