绝世好题(动态规划+位运算)
给定一个长度为 \(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);
}