题目描述
给定一个长度为 (n) 的数列 (a_i),求 (a_i) 的子序列 (b_i) 的最长长度 (k),满足 (b_i & b_{i-1} e 0),其中 (2leq ileq k), (&) 表示位运算取与。
输入格式
输入文件共 (2) 行。 第一行包括一个整数 (n)。 第二行包括 (n) 个整数,第 (i) 个整数表示 (a_i)。
输出格式
输出文件共一行。 包括一个整数,表示子序列 (b_i) 的最长长度。
输入输出样例
输入 #1
3
1 2 3
输出 #1
2
说明/提示
对于(100\%)的数据,(1leq nleq 100000),(a_ileq 10^9)。
分析
看到题的第一反应就被标签里的枚举给迷惑到了,然后快速打了一个暴力枚举,显然直接(90)分(TLE)了,然后开始想正解。
首先题目中的条件是含有位运算的,我们就可以从这里开始入手优化。
因为题目中给的条件是(a_i&a_{i-1} eq 0),所以这就证明了只要两个数之间有一个二进制位上相同的(1)就可以让长度加一,那么我们就可以枚举每一个二进制位,找出他们与运算后是否为(0),如果不是(0)就让长度加一,我们的一个状态转移方程就出来了:
[Max = max(dp[c]+1,Max)
]
其中(dp[c])是二进制第(c)位时最大的长度,当前状态下满足条件就加一,然后与最大长度(Max)取最大值。
需要注意的一个地方就是我们在状态转移后需要每次更新一下(dp[c]),因为当前满足条件的最长长度已经找出来是(Max)了,所以所有当前满足条件的二进制位的最大长度都变为(1),那么就又有一个转移:
[dp[c]=Max
]
最后再统计一下所有更新后(Max)的最大值就好了。
代码
首先先放一下我的暴力(90points)代码
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5+10;
int n,a[maxn],ans;
int dp[maxn];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;++i){
scanf("%d",&a[i]);
}
for(int i=1;i<=n;++i){
dp[i]=1;
for(int j=1;j<i;++j){
if((a[i]&a[j])!=0)dp[i]=max(dp[i],dp[j]+1);
}
ans=max(ans,dp[i]);
}
printf("%d",ans);
return 0;
}
然后是正解:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 32;
int dp[maxn];
int Max,ans;
int main(){
int n;
scanf("%d",&n);
for(int i=1;i<=n;++i){
int b;
scanf("%d",&b);
for(int c=0;c<=31;++c){//枚举每一个二进制位
if((1<<c)&b)Max=max(Max,dp[c]+1);
}
for(int c=0;c<=31;++c){//更新满足条件的二进制位的最大值
if((1<<c)&b)dp[c]=max(Max,dp[c]);
}
ans=max(ans,Max);//统计最大值答案
}
cout<<ans<<"
";
return 0;
}