链接:CodeForces -1208F Bits And Pieces
题意:
给出一个长度为 n    ( 3 ≤ n ≤ 1 0 6 ) n\;(3\le n\le 10^6) n(3≤n≤106) 的序列 a 1 , a 2 , ⋯   , a n    ( 0 ≤ a i ≤ 2 ⋅ 1 0 6 ) a_1,a_2,\cdots,a_n\;(0\le a_i\le 2\cdot10^6) a1,a2,⋯,an(0≤ai≤2⋅106),求 a i ∣ ( a j & a k )    ( 其 中 i < j < k ) a_i|(a_j\And a_k)\;(其中 i\lt j\lt k) ai∣(aj&ak)(其中i<j<k) 的最大值。
分析:
假设固定一个 a i a_i ai,对于为 1 1 1的二进制位,或运算一定得 1 1 1,不用考虑;但对于为 0 0 0的二进制位,只有和 1 1 1或运算才能使得结果值更大,也就是说要 a j a_j aj 和 a k a_k ak 的对应二进制位为 1 1 1(因为 a i a_i ai和 a j a_j aj之间是与运算)。
例如当 a i = 0100   1101 a_i=0100\,1101 ai=01001101(假设 a ≤ 2 8 − 1 a\le2^8-1 a≤28−1),则要令值最大应当尽可能找到 a j a_j aj和 a k a_k ak,其子集均包括 1011   0010 1011\,0010 10110010。
①dp过程
这里就要用到SoS DP的思想:https://codeforces.com/blog/entry/45223
d p [ x ] dp[x] dp[x]:序列中 子集含有 x x x的 a a a的位置集合,由于 i < j < k i\lt j\lt k i<j<k,所以只要贪心地 保留位置最靠右(最大)的两个位置 即可。
所以可以先将所有 a a a放入 d p dp dp数组当中,对于 x x x,其 子集有 2 k 2^k 2k个(其中 k k k为 x x x二进制位为 1 1 1的个数),若对于每一个 d p [ a ] dp[a] dp[a],都找到所有子集并更新,时间复杂度会达到 O ( N ∗ a ) O(N*a) O(N∗a)。
由于很多 a a a都有相同的子集,所以多了很多重复更新。我们可以考虑,一个数,其子集的子集,仍然是其子集,所以可以 每次仅更新将 其中一位二进制位从 1 1 1变为 0 0 0 的子集(之后这些子集再去更新下一层子集)。例如 a = 1101 a=1101 a=1101,则仅更新 0101 , 1001 , 1100 0101,1001,1100 0101,1001,1100。
由于子集肯定小于原数,那么可以从最大的 a m a x a_{max} amax开始,从后往前更新 0 0 0 ~ a m a x a_{max} amax的所有数,时间复杂度为 O ( a ∗ k ) O(a*k) O(a∗k)。
②求解答案
枚举 a i a_i ai,根据 a i a_i ai的二进制位 贪心地构造一个值 w w w,保证 d p [ w ] dp[w] dp[w]的两个最大位置均 > i \gt i >i,最大的 a i ∣ w a_i|w ai∣w即为答案。
以下代码:
#include<bits/stdc++.h>
#define LL long long
#define PII pair<int,int>
using namespace std;
const int INF=0x3f3f3f3f;
const int maxn=1e6+50;
const int maxa=2e6+50;
int n,a[maxn];
PII dp[maxa];
void updata(int x,int pos)
{
if(dp[x].first<pos)
{
dp[x].second=dp[x].first;
dp[x].first=pos;
}
else if(dp[x].second<pos&&pos!=dp[x].first) //注意first和second的位置不能相同
dp[x].second=pos;
}
void get_dp()
{
for(int x=maxa-1;x>=0;x--) //从大到小更新
{
for(int i=20;i>=0;i--)
{
if(((x>>i)&1)==1) //枚举x为1的二进制位
{
updata(x^(1<<i),dp[x].first); //更新 把x的一个为1的二进制位变为0的子集
updata(x^(1<<i),dp[x].second);
}
}
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
updata(a[i],i);
}
get_dp();
int ans=0;
for(int i=1;i<=n-2;i++)
{
int w=0;
for(int j=20;j>=0;j--)
{ //找到a[i]为0的二进制位
if(((a[i]>>j)&1)==0&&dp[w|(1<<j)].first>i&&dp[w|(1<<j)].second>i)
w=w|(1<<j);
}
ans=max(ans,a[i]|w);
}
printf("%d",ans);
return 0;
}