题目链接
题目大意
给你一个长度为(n(2leq nleq 1e6))的数组
从中选出一个子序列(a)
那么求出(max sum_{i=2}^{i=len[a[i]]} a[i]&a[i-1])
题目思路
参考官方题解
设(dp[i])表前(i)个子序列的最大答案
首先很容易想到一个(O(n^2))的(dp)
但是显然这样会超时
这样你肯定会想到用二进制优化(dp),然后就没有然后了。。。
此题真的有点神奇
下面我将进行不太严格的证明
定义(h(x))为不大于x的最大2的幂次
假设我选了最优子序列
(f_{j1},f_{j2}...f_{jk})
设(j1leq temp leq j2)
那么必然在不存在(h(f_{temp}&f_{j1})=h(f_{j2}&f_{j1}))
原因是为什么
为了方便设(x=f_{j1},y=temp,z=f_{j2})
那么假设我(y)存在
那么序列变成(x,y,z,....)
若不在序列则变成(x,z....)
显然答案的变化为
(x&y+y&z-x&z)
显然这个值是大于0的,那么放入(y)才是最优选择
而这个序列却已经是最优序列,代表中间没有(y)
也就是说我们其实只要考虑二进制中的每一位的最后出现的位置即可
因为这个二进制的这一位肯定包含和这个元素和他前面的元素的(&)的最高位
代码
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
typedef long long ll;
typedef pair<double,int> pdi;
const ll INF=0x3f3f3f3f3f3f3f3f;
const int maxn=1e6+5,mod=1e7;
int n;
ll a[maxn];
ll dp[maxn];
int last[70];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
}
ll ans=0;
for(int i=1;i<=n;i++){
dp[i]=dp[i-1];
for(int j=0;j<=60;j++){
if(a[i]&(1ll<<j)){
dp[i]=max(dp[i],dp[last[j]]+(a[i]&a[last[j]]));
last[j]=i;
}
}
}
printf("%lld
",dp[n]);
return 0;
}