【DP,位运算】 Bank Security Unification

传送门:https://codeforces.com/gym/102956/problem/D

题目大意:给出一个数列 \(a\) ,选出一个子序列 \(a_k\) ,使得子序列(长度记为 \(len\)\(\sum_{i=1}^{len-1}a_{k_i}\&a_{k_i+1}\) 最大。

分析

我们记 \(f[i][j]\) 为处理到第 \(i\) 位,长度为j的子序列的最大值

若第i位不选 \(f[i][j]=f[i-1][j]\)
否则 \(f[i][j]=max(f[pre][j-1])\) 其中 \(pre\) 表示 \(i\) 之前所有可能的下标。

采取滚动数组的思想,我们可以优化一维,进而得到:
\(f[i]=max(f[i-1],f[pre])\)

但是直接枚举 \(pre\) 需要 \(o(N)\) 复杂度,显然超时,我们从位运算的角度考虑优化:

我们记 \(highbit(x)\)是我乱起的)为二进制中 \(x\) 除了最高位为 \(1\) 其余全部清 \(0\) 所对应的数,例如:\(highbit(11001)=10000\)

那么我们有这样一个结论:对于选出的所求值最大的子序列最后的两个数 \(a_i,a_j\) 不存在 \(a_t(t \in (i,j))\),使得\(highbit(a_i\&a_j)=highbit(a_i\&a_t)\)。(反证法易得)

这意味着 \(f[i]\) 只可能由 \(f[pre[j]]\) 转移而来,这里 \(pre[j]\) 表示离 \(i\)最近的,对应二进制位置 \(j\)\(1\) 的数所对应的下标。(结合代码理解)

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int N=1e6+5;
ll f[N], pre[N];
int n;
ll a[N];

int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
	
	for(int i=1;i<=n;i++){
		f[i]=f[i-1];
		for(int j=0;j<=40;j++){
			if(a[i]>>j&1){
				f[i]=max(f[i],f[pre[j]]+(a[pre[j]]&a[i]));
				pre[j]=i;
			}
		}
	}
	
	cout<<f[n]<<endl;
	
	return 0;
}
posted @ 2021-03-30 21:52  HinanawiTenshi  阅读(94)  评论(0编辑  收藏  举报