题目大意
对于一个序列 $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]; 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]); }
|