题目大意
对于一个序列 a1,⋯,an,定义 fi 表示序列前 i 项依次进行按位与运算后的值。一个序列的价值为 ∑i=1nfi。
现在给你一个序列 a1,⋯,an,你需要把它重新排列,使得序列价值尽量大。
n,ai≤106
解法
一个序列的 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]); }
|