绝世好题(动态规划+位运算)

给定一个长度为 \(n\) 的数列 \(a_i\),求 \(a_i\) 的子序列 \(b_i\) 的最长长度 \(k\),满足 \(b_i\ \&\ b_{i-1} \ne 0\),其中 \(2\leq i\leq k\)\(\&\) 表示位运算取与。\(1\le n\le 10^5,1\le a_i\le 10^9\)

\(O(n^2)\) 的暴力是显然的,有转移方程 \(dp_i=\max\limits_{j=1,a_i\& a_j\ne 0}^{i-1}\{dp_j+1\}\),可以获得 80 到 90 分。然而,这个式子没有单调队列、斜率优化等常见的特点,我们只能考虑从位运算性质入手优化。

现在的 \(dp_i\) 意味着 \(i\) 结尾的最长子序列长度,这一状态与位运算很难建立联系。为什么叫绝世好题啊?我们可以转换思路,设计 \(dp_i\) 表示最后一项二进制形式下第 \(i\) 位为 \(1\) 的最长子序列长度。如果感到难以理解,请仔细反复阅读上句定义,抽离关键词,务必明白其意义。

每当我们读入一个数 \(a_i\) 后,我们枚举其二进制位 \(j\),求出每一个为 \(1\) 的二进制位对应的 dp 值的最大值(因为 \(1\& 1\),所以这时更新合法),\(1\) 后反过来更新这些 dp 值

这种状态设计首次遇到确实难以想出,只能慢慢积累了。

下面是 AC 代码片段:

for(int i=1,x,k;i<=n;++i) 
{
	scanf("%d",&x),k=1;
	for(int j=0;j<=30;++j) if((1<<j)&x) k=max(k,dp[j]+1);
	for(int j=0;j<=30;++j) if((1<<j)&x) dp[j]=max(dp[j],k);
	ans=max(ans,k);
}

THE END

posted @ 2021-10-20 20:34  q0000000  阅读(54)  评论(0编辑  收藏  举报