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

点此看题面

大致题意: 给你一个序列(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;//输出答案
}
原文地址:https://www.cnblogs.com/chenxiaoran666/p/BZOJ4300.html