【JZOJ4937】与运算 题解

题目大意

  对于一个序列 a1,,ana_1,\cdots,a_n,定义 fif_i 表示序列前 ii 项依次进行按位与运算后的值。一个序列的价值为 i=1nfi\sum_{i=1}^n f_i
  现在给你一个序列 a1,,ana_1,\cdots,a_n,你需要把它重新排列,使得序列价值尽量大。
  n,ai106n, a_i \le 10^6

解法

  一个序列的 ff 值一定是成段的,可以根据这个进行 dp。设 g[i]g[i] 表示当前 ff 值为 ii 的时候的最大答案。那么转移就是枚举一个 jj,然后 g[i]=max(g[j]+i(cnt[i]cnt[j]))g[i]=max(g[j]+i*(cnt[i]-cnt[j])),其中 cnt[x]cnt[x] 表示有多少个 a[i]a[i] 包含了 xx 这个状态。

  所以现在其实就是要求 cntcnt,可以用分治来求。(UPD:若干年过去了,现在知道这东西叫高维前缀和)

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;

typedef long long LL;

const int maxn=1e6+5, MX=25, maxN=2e6+5;

int n,N,a[maxn],er[MX];

LL f[maxN]; //这里的f就是上述的cnt
void dfs_f(int l,int r)
{
if (l==r) return;

int mid=(l+r)>>1;
dfs_f(l,mid), dfs_f(mid+1,r);

int enc=mid+1-l;
fo(i,l,mid) f[i]+=f[i+enc];
}

LL g[maxN];
int main()
{
scanf("%d",&n);
N=(1<<20)-1;
fo(i,1,20) er[i]=1<<(i-1);
fo(i,1,n) scanf("%d",&a[i]);

fo(i,1,n) f[a[i]]++;
dfs_f(0,N);

fd(i,N-1,0)
fo(j0,1,20) if ((i&er[j0])==0)
{
int j=i|er[j0];
g[i]=max(g[i],g[j]+i*(f[i]-f[j]));
}

printf("%lld\n",g[0]);
}