按位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。
感悟:
关于位运算或取与不取俩种状态都可以想到进制,二进制只需要几十位就可以组成好多数,极大节约时间和空间。

posted @ 2026-08-11 16:16  lucky_lh  阅读(11)  评论(0)    收藏  举报