楔子
在做题的时候,我们总是可以遇到各种各样的序列问题:导弹拦截,LIS, LCS, LICS……这里总结的一般是一些基础的序列问题变形。
1. P4310 绝世好题
题目大意:给定一个长度为n的数列ai,求ai的子序列bi的最长长度,满足bi&bi-1!=0(2<=i<=len)。 1<=n<=100000,ai<=10^9。
暴力的做法:我觉得可以联系一下LIS,只不过是这道题把小于的条件变成是了bi&bi-1!=0。
但是,我们可以再看一下这道题的数据范围,如果按照原来的写法肯定不可以拿到满分,那么,我们考虑一下如何优化??
首先,可以把状态转移方程写出来:
f
[
i
]
=
m
a
x
(
f
[
j
]
+
1
)
f[i]=max(f[j]+1)
f[i]=max(f[j]+1) j∈{ a[i]&a[j]!=0, j<i }
那么,我们关心的就是在i之前的和a[i]&之后有值的那些数字。
那么对于max而言,我们就可以考虑用大根堆把 在i之前的数字以及其 f值 都存起来,之后再每次查询的时候每次都弹出堆顶就可以了。
此时在考虑一下后面的限制条件:当堆顶不满足的时候,我们就可以把它弹出,之后寻找下一个堆顶;但是,此时弹出的堆顶是可能对之后的数字是有价值的,所以,在该数字查询的时候,我们把堆顶弹出,查询之后,我们再把弹出的数字压入堆中就可以完成这个操作了。
时间复杂度比较玄学,这道题是可以卡过了的。
思考一波正解:因为&的操作是基于二进制进行操作的,所以,我们可以对每个数字的二进制数字进行DP。
转自洛谷的正解
令dp[i]表示数列到目前为止最后一项第i位为1的最大子序列长度,每读入一个数时就大力转移。一个数可以被它所有的二进制位的dp值转移,然后把它转移到它的所有二进制位的dp值上。
因为每个数字可以转移的情况只能是两个数字之中有一个二进制位的数字都为1,所以,我们的转移只转移二进制位为1的即可,所以,正解的正确性是可以保证的。
code
暴力
#include<bits/stdc++.h>
using namespace std;
const int nn=100003;
priority_queue<pair<int, int> >q;
int n, a[nn], f[nn], b[nn], ans=1;
//大根堆中存的是最长的子序列的长度以及该子序列的最后一个数字的标号
inline int read()
{
int x=0,f=1; char ch=getchar();
while(ch>'9'||ch<'0'){if(ch='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
int main()
{
n=read(); f[1]=1;
for(int i=1;i<=n;++i) a[i]=read();
q.push(make_pair(f[1], 1));
for(int i=2;i<=n;++i)
{
int cnt=0, x;
while(q.size())
{
x=q.top().second;
if(a[x]&a[i])
{
f[i]=f[x]+1; ans=max(ans, f[i]);
q.push(make_pair(f[i], i));
break;
}
b[++cnt]=x; q.pop();
}
for(int j=1;j<=cnt;++j)
q.push(make_pair(f[b[j]], b[j]));
}
cout<<ans<<endl;
return 0;
}
正解
#include<bits/stdc++.h>
using namespace std;
int n, f[33], ans=1;
int main()
{
cin>>n; int a;
for(int i=1;i<=n;++i)
{
cin>>a; int k=1;
for(int j=0;j<=32;++j)
if(a&(1<<j)) k=max(f[j]+1, k);
for(int j=0;j<=32;++j)
if(a&(1<<j)) f[j]=k;
ans=max(ans, k);
}
cout<<ans<<endl;
return 0;
}
2
题目意思: