HYSBZ(BZOJ) 4300 绝世好题(位运算,递推)

HYSBZ(BZOJ) 4300 绝世好题(位运算,递推)

Description

给定一个长度为n的数列ai,求ai的子序列bi的最长长度,满足bi&bi-1!=0(2<=i<=len)。

Input

输入文件共2行。
第一行包括一个整数n。
第二行包括n个整数,第i个整数表示ai。

Output

输出文件共一行。
包括一个整数,表示子序列bi的最长长度。

Sample Input

3
1 2 3

Sample Output

2

Http

HYSBZ:http://www.lydsy.com/JudgeOnline/problem.php?id=4300

Source

递推,位运算

解决思路

初看这道题目,觉得有点像最长不下降子序列,但是又有些不同。

在这里我们要巧妙地运用题目中给出的位运算的条件。
首先我们看题目中对b序列的x要求:

[b_i&b_{i-1}!=0 ]

这也就是说,如果我们把每一个数都转成2进制,那么要求两个数至少有相同的一位都是1。
什么意思,还是画图来分析:
此处输入图片的描述
现在我们有这两个数都转成二进制存在上面,如果他们有相同的一位都是1,即可以连接(如下图)
此处输入图片的描述
这样的两个数就可以连接。
而若是像下图这样,就不可以连接:
此处输入图片的描述
跟据上面我们的分析,我们设一个数组Solve,Solve第i位表示如果接在二进制的第i位上现在能接的最长长度。
那么如何计算Solve呢?
对于每一个数(假设是x),我们从低位到高位依次扫描x中是1的位置,取出对应的Solve数组中的最大值,然后把Solve中的这些值全部更新为这个最大值+1。
直接这么说可能会有些头大,我们还是借助图来说明一下。
假设我们现在有这样一个例子,左边是Solve数组,右边是x转成二进制后的样子
此处输入图片的描述
现在我们框出对应X的位上为1的Solve数组的数,并选出最大值(红框框起来的就是选出的数,黄框是最大值):
此处输入图片的描述
将所有框中(zhong4声)的Solve的值都更新为最大值+1
此处输入图片的描述
这样处理每一个数,最后Solve数组中最大的就是结果了。

为什么这样是对的呢?
参考一下我们最长不下降子序列的做法,我们是设F[i]表示以i结尾的最长不下降子序列的长度,那么在这里,Solve[i]表示的是二进制以第i位为真结尾的最长满足题意的子序列。所以,我们这样做是对的。

另外可能产生疑问的一个地方就是我们为什么更新答案的时候是所有是1的那一位的答案都要更新成最大值+1呢?
因为根据我们对Solve的定义,既然这一位是1并且通过另外的方式连过来的长度更长,尽管并不是从这一位连过去的,但从定义出发,这里也是可以的。

这个地方不是很好理解,如果还有疑问,可以在留言区留言,博主会尽量回复。
PS:话说这真是一道绝世好题

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
 
const int maxN=100001;
const int inf=2147483647;
 
int n;
int A[maxN];
int Solve[40]={0};
 
int main()
{
    cin>>n;
    for (int i=1;i<=n;i++)
        cin>>A[i];
    int Ans=0;
    for (int i=1;i<=n;i++)
    {
        int now_ans=1;
        for (int j=0,sum=1;j<=40;j++,sum=sum<<1)//取出最大值
            if (sum&A[i])
                now_ans=max(now_ans,Solve[j]+1);
        for (int j=0,sum=1;j<=40;j++,sum=sum<<1)//把所有在x上这一位是1的都更新成当前最大值+1
            if (sum&A[i])
                Solve[j]=max(Solve[j],now_ans);
        Ans=max(now_ans,Ans);//更新最后要输出的最大值
    }
    cout<<Ans<<endl;
    return 0;
}
原文地址:https://www.cnblogs.com/SYCstudio/p/7206297.html