按位DP-洛-绝世好题
P4310 绝世好题
题目描述
给定一个长度为 \(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
输入 #1
3
1 2 3
输出 #1
2
说明/提示
对于 \(100\%\) 的数据,\(1\leq n\leq 100000\),\(a_i\leq 10^9\)。
想法
一开始,类比于最长上升子序列的简单想法,二维枚举dp,TLE;有位运算应该想到数位DP,%&%要求俩个同数位都是1,用35个二进制即可表示所有数,复杂度35*n。
代码:
点击查看代码
#include <bits/stdc++.h>
using namespace std;
int n;
int ans;
int a[100010],dp[35]; //最低位开始出现1的最大
int main()
{
cin>>n;
for(int i = 1;i <= n;i++) cin>>a[i];
for(int i = 1;i <= n;i++)
{
int maxv = 0; //储存数位是1的最大dp
for(int j = 0;j < 35;j++)
{
if(a[i] & (1<<j)) maxv = max(maxv,dp[j]);
}
for(int j = 0;j < 35;j++)
{
if(a[i] & (1<<j)) dp[j] = maxv+1;
}
}
for(int i = 0;i <= 34;i++) ans = max(ans,dp[i]);
cout<<ans;
return 0;
}
细节:
关于状态转移需要注意,我一开始是if(a[i] & (1<<j)) dp[j]++ ,没有选最大和赋值更新数位是1位置为当前最大,这样不能体现相邻只有一个数位共1就连,反而是最长子序列都有一个公共的数位是1。本题写法体现交叉连接最大dp。
感悟:
关于位运算或取与不取俩种状态都可以想到进制,二进制只需要几十位就可以组成好多数,极大节约时间和空间。

浙公网安备 33010602011771号