【BZOJ4300】绝世好题(位运算水题)

点此看题面

大致题意: 给你一个序列\(a\),让你求出最长的一个子序列\(b\)满足\(b_i\&b_{i-1}!=0\)

位运算+\(DP\)

考虑设\(f_i\)表示以第\(i\)个数为结尾所能得到的合法子序列的最长长度

则一个数能从另一个数那里转移,当且仅当这两个数按位与的值不为\(0\)

考虑按位与的值不为\(0\),实际意义就是二进制下存在至少一位上这两个数都是\(1\)

那么,我们可以枚举两个位置,然后枚举二进制下一位判断是否可以转移。

这样就可以轻松得出一个复杂度比暴力还劣的\(O(n^2log\ a_i)\)的解法。

实际上,在刚才的转移中其实有许多无意义转移。

则我们需要知道,怎样的转移是有意义的。

假设有\(l,r(1\le l<r\le 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;//输出答案
}

猜你喜欢

转载自www.cnblogs.com/chenxiaoran666/p/BZOJ4300.html