【xsy2272】 与运算 状压dp

时间:2023-03-09 18:07:46
【xsy2272】 与运算 状压dp

题目大意:给你一个长度为$n$的序列$a$,我们定义$f_i$表示序列$a$前i项一次进行按位与运算后的值。

我们认为一个序列的价值为$\sum_{i=1}^{n}f_i$,现在你要重新排列序列$a$,使得序列的价值最大。

数据范围,$1≤a_i,n≤10^6$

我们考虑$dp$。

不难发现,若序列中存在数$x$和数$y$,满足$x\&y==x$,那么将$y$放在$x$前面显然是会更优的。

设$cnt[i]$表示序列$a$中,有多少个数$k$,满足$i\&k==i$(此处&表示按位与)

我们$f[i]$表示以i结尾的序列的最大值是多少,那么显然答案为$\max\limits_{0≤i<2^{20}}f[i]$

不难发现有$f[i]=\max\limits_{i\&k==i} f[k]+(cnt[k]-cnt[i])$。

如果说直接转移的话复杂度显然是$O(n^{\log_2^3})$的,这么搞只能过$70%$的数据。

所以要稍微考虑下转移的性质,我们只需要转移满足$k=i+2^j$的$k$即可。

这样时间复杂度就可以优化到$O(n\log\ n)$了。

 #include<bits/stdc++.h>
#define N 20
#define M (1<<N)
using namespace std;
int cnt[M]={}; long long f[M]={},ans=;
int main(){
for(int n,x=scanf("%d",&n);n;n--) scanf("%d",&x),cnt[x]++;
for(int j=;j<N;j++)
for(int i=M-(<<(j+));i>=;i--)
if((i&(<<j))==)
cnt[i]+=cnt[i+(<<j)];
for(int i=M-;~i;i--){
for(int j=;j<N;j++)
if((i&(<<j))==){
f[i]=max(f[i],f[i+(<<j)]+1LL*i*(cnt[i]-cnt[i+(<<j)]));
ans=max(ans,f[i]);
}
}
cout<<ans<<endl;
}