P4310 绝世好题 [位运算优化dp]

题目描述

给定一个长度为 \(n\) 的数列 \(a_i\)​,求 \(a_i\)​ 的子序列 \(b_i\)​ 的最长长度 \(k\),满足 \(b_i \& b_{i-1} \ne 0\),其中 \(2\leq i\leq k\)\(\&\) 表示位运算取与。

输入格式

输入文件共 \(2\) 行。 第一行包括一个整数 \(n\)。 第二行包括 \(n\) 个整数,第 \(i\) 个整数表示 \(a_i\)​。

输出格式

输出文件共一行。 包括一个整数,表示子序列 \(b_i\)​ 的最长长度。

输入输出样例

输入 #1

3
1 2 3

输出 #1

2

说明/提示

对于\(100\%\)的数据,\(1\leq n\leq 100000\)\(a_i\leq 10^9\)

分析

看到题的第一反应就被标签里的枚举给迷惑到了,然后快速打了一个暴力枚举,显然直接\(90\)\(TLE\)了,然后开始想正解。

首先题目中的条件是含有位运算的,我们就可以从这里开始入手优化。

因为题目中给的条件是\(a_i\&a_{i-1}\neq 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<<"\n";
	return 0;
}
posted @ 2020-07-08 18:41  Vocanda  阅读(168)  评论(0编辑  收藏  举报