大致题意: 给你一个序列(a),让你求出最长的一个子序列(b)满足(b_i&b_{i-1}!=0)。
位运算+(DP)
考虑设(f_i)表示以第(i)个数为结尾所能得到的合法子序列的最长长度。
则一个数能从另一个数那里转移,当且仅当这两个数按位与的值不为(0)。
考虑按位与的值不为(0),实际意义就是二进制下存在至少一位上这两个数都是(1)。
那么,我们可以枚举两个位置,然后枚举二进制下一位判断是否可以转移。
这样就可以轻松得出一个复杂度比暴力还劣的(O(n^2log a_i))的解法。
实际上,在刚才的转移中其实有许多无意义转移。
则我们需要知道,怎样的转移是有意义的。
假设有(l,r(1le l<rle n))满足(a_l)与(a_r)二进制下第(j)位上都为(1)。
则根据前面的转移,(r)必然可以由(l)转移,则(f_r)至少为(f_l+1),简而言之就是(f_r>f_l)。
也就是说,对于二进制下第(k)位为(1)的任何的位置(i)((i>r)),从(l)转移显然是无意义的。
其实,对于每一个(j),只有从二进制下这一位为(1)的最靠右的位置转移才是有意义的。
因此,我们设(g_j)表示二进制下第(j)位为(1)的最右位置,转移方程即为(转移时要满足(a_i)二进制下第(j)位为(1)):
[f_i=max_{j=1}^{30}f_{g_j}+1
]
转移完之后,我们再次枚举每一个满足(a_i)二进制下第(j)位为(1)的(j),然后更新(g_j=i)即可。
代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define Gmax(x,y) (x<(y)&&(x=(y)))
using namespace std;
int n,a[N+5],f[N+5],g[N+5];
int main()
{
RI i,j,ans=0;for(scanf("%d",&n),i=1;i<=n;++i) scanf("%d",a+i);
for(i=1;i<=n;++i)
{
for(j=30;~j;--j) a[i]>>j&1&&Gmax(f[i],f[g[j]]);++f[i];//转移
for(j=30;~j;--j) a[i]>>j&1&&(g[j]=i);Gmax(ans,f[i]);//更新
}return printf("%d",ans),0;//输出答案
}