题目大意
对于一个序列
a1,a2,...,an
,定义
fi
表示序列前
i
项依次进行按位与运算后的值。定义一个序列的价值为
现在给定一个序列
a1,a2,...,an
,你需要把它重新排列,求序列的最大价值。
1≤n,ai≤1000000
题目分析
可以发现,
fi
随着
i
的增大在不断地变小,变成它的子集(将二进制看成集合)。
因此
考虑使用
dp
来计算答案,令
Fi
表示
f
值为
Fi=maxi⊂j{Fj+i×(cnti−cntj)}
在这里, cnti 表示包含二进制 i 的
可是这样直接计算是 O(n2) 的。考虑枚举子集来转移,那么时间复杂度是 O(nlog23) 的。
怎样才能更快呢?其实我们只需要从只比 i 多一位
这样会不会有弄不出来的值成了最优答案呢?其实不会,因为如果这个值弄不出来,要么就包含能够弄出来的数,这样它一定是作为转移的中间媒介,不可能最优,要么是完全弄不出来的数,那它的值一定是 0 。
至于计算
总时间复杂度 O(nlogn) 。
代码实现
#include <iostream>
#include <cstdio>
#include <cctype>
using namespace std;
typedef long long LL;
int read()
{
int x=0,f=1;
char ch=getchar();
while (!isdigit(ch)) f=ch=='-'?-1:f,ch=getchar();
while (isdigit(ch)) x=x*10+ch-'0',ch=getchar();
return x*f;
}
const int N=1000005;
const int A=1<<20;
int cnt[A];
int a[N];
LL f[A];
int n,sum;
void count(int l,int r)
{
if (l==r) return;
int mid=l+r>>1;
for (int i=mid+1;i<=r;i++) cnt[l+(i-mid)-1]+=cnt[i];
count(l,mid),count(mid+1,r);
}
void dp()
{
f[A]=1;
for (int s=A-1;s>=0;s--)
for (int i=0;i<20;i++)
if (!(s&(1<<i)))
f[s]=max(f[s],f[s|(1<<i)]+1ll*s*(cnt[s]-cnt[s|(1<<i)]));
}
int main()
{
freopen("and.in","r",stdin),freopen("and.out","w",stdout);
n=read(),sum=A-1;
for (int i=1;i<=n;i++) cnt[a[i]=read()]++,sum&=a[i];
count(0,A-1),dp();
printf("%lld\n",f[sum]);
fclose(stdin),fclose(stdout);
return 0;
}