bzoj4300 绝世好题(位运算+DP)
为什么要写这道题呢?因为它是“绝世好题”。
题意:给定n个数,在其中找出一段子序列b,使得b[i]&b[i-1]!=0,求出满足条件的最长子序列长度。
输入:第一行:一个整数n,表示数列的个数。
第二行:n个整数,表示数列a。
输出:一行,一个整数,表示最长的子序列长度。
输入样例:
3
1 2 3
输出样例:
2
解析:若b[i]&b[i-1]!=0,即b[i]与b[i-1]在二进制下有一位相同且都为1。那么设dp[i]表示前i个数所能达到的最长子序列长度,那么在转移时只需枚举是从二进制下的第几位转移过来的便好了。由于在转移时还要枚举是从哪个位置转移过来的,所以时间复杂度为o(n^2log(n)),但由于每次进行转移时是在前面的状态中找一个最大的,所以显然可以用堆进行优化,时间复杂度(o(nlog(n)^2))。(这题其实有o(nlog(n))的做法,而且大家好像都是这么写的,可我做的时候没想到(果然我还是菜!))。
代码如下:
1 #include<cstdio> 2 #include<vector> 3 #include<queue> 4 using namespace std; 5 6 const int MAXN=100010; 7 int n,a[MAXN],dp[MAXN],ans; 8 vector <int> ve[MAXN]; 9 priority_queue <int> heap[33]; 10 11 int read(void) { 12 char c; while (c=getchar(),c<'0' || c>'9'); int x=c-'0'; 13 while (c=getchar(),c>='0' && c<='9') x=x*10+c-'0'; return x; 14 } 15 16 void solve(int x,int p) { //预处理出二进制下1的个数 17 int bit=0; 18 while (x>0) { 19 bit++; 20 if (x&1) { 21 ve[p].push_back(bit); 22 } 23 x>>=1; 24 } 25 } 26 27 int main() { 28 n=read(); 29 for (int i=1;i<=n;++i) a[i]=read(); 30 for (int i=1;i<=n;++i) solve(a[i],i); 31 for (int i=1;i<33;++i) heap[i].push(0); 32 for (int i=1;i<=n;++i) { 33 for (int j=0;j<ve[i].size();++j) { 34 int u=ve[i][j]; 35 dp[i]=max(dp[i],heap[u].top()+1); //从堆中取出一个最大的进行转移 36 } 37 for (int j=0;j<ve[i].size();++j) //将这一个状态加入堆中 38 heap[ve[i][j]].push(dp[i]); 39 } 40 for (int i=1;i<=n;++i) //找出最长的子序列 41 for (int j=0;j<ve[i].size();++j) ans=max(ans,dp[i]); 42 printf("%d",ans); 43 return 0; 44 }