CF1208F Bits And Pieces(贪心)

题意

给定 \(n\) 个数的数组 \(d\),找到 \(i < j < k\)\(i,j,k\),使得 \(d_i|(d_j \& d_k)\) 最大。

数据范围

\(3 \leq n \leq 10^6,0 \leq d_i \leq 2 \times 10^6\)

思路

思路来源于同机房的城阙巨佬。

注意到或运算的性质,只要两个数之间有一个数的第 \(i\) 位为 \(1\),那么得到的结果中的第 \(i\) 位就为 \(1\)。于是就可以考虑枚举 \(d_i\)。而对于 \(d_j \& d_k\),我们只关心 \(d_i\) 在二进制下为 \(0\) 的位数 \(d_j \& d_k\) 得到的结果是否为 \(1\)。更一般的,根据贪心的性质,位数越高的为 \(1\) 得到的答案也就越大。而 \(d_i\) 本身就为 \(1\) 的位数就并不关心。

于是就可以考虑用一个数组统计 \(+\infty\) 的所有子集的出现次数,如果一个子集出现了两次,那么就必然存在 \(d_j\)\(d_k\) 使得它们 \(\&\) 得到的集合中包含这个集合。

考虑如何更新子集出现的次数,我们只需要一个集合出现两次,再多也就没有意义了,但直接循环枚举,单次更新的复杂度达到了 \(O(2^{23})\)\(2 \times 10^6\) 在二进制下连第 \(0\) 位一共有 \(23\) 位)。于是可以考虑 DFS 更新,如果 \(d_i\) 的一个子集已经被更新了两次,那么它的所有子集也必定都被更新了两次,此时直接返回就可以。但是需要注意一个数被更新两次的情况(如从 \(111\) 搜索到 \(110\)\(101\),它们又会各自枚举一次 \(100\)),于是每一次更新的时候要开一个数组记录一下每个子集是否已经被枚举过。

而对于每一个 \(d_i\),从高到低枚举每一位(因为 \(100000>011111\)),同时用一个变量 \(now\) 记录当前要求的 \(d_j\&d_k\) 的集合的子集。如果当前位为 \(1\),那就直接更新 \(d_i\) 得到的答案的最大值,如果为 \(0\),那么就判断将 \(now\) 当前位上更新成 \(1\) 后的集合后,是否在 \(d_j\&d_k\) 里出现了两次。最终取所有 \(d_i\) 得到的答案的最大值即可。

code:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=3e6+10;
bool vis[N];int a[N],n,cnt[N],st[N],top,ans;
void update(int val)
{
	if(cnt[val]>=2||vis[val]) return ;cnt[val]++;st[++top]=val;vis[val]=true;
	for(int i=22;i>=0;i--) if(val>>i&1) update(val^(1<<i));
}
int query(int val)
{
	int res=0,now=0;
	for(int i=22;i>=0;i--)
	{
		if(val>>i&1) res|=1<<i;
		else if(cnt[now|(1<<i)]>=2) res|=1<<i,now|=1<<i;
	}
	return res;
}
void clear(){while(top) vis[st[top--]]=false;}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);update(a[n]);clear();
	for(int i=n-1;i>=2;i--)
	{
		update(a[i]);clear();
		ans=max(ans,query(a[i-1]));
	}
	printf("%d\n",ans);
	return 0;
}
posted @ 2021-11-12 07:50  曙诚  阅读(72)  评论(0)    收藏  举报