【JZOJ4937】与运算 题解

题目大意

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

解法

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

  所以现在其实就是要求 $cnt$,可以用分治来求。(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]);
}