引子:
那天…我们和初二的同学们一起做了题。
T1 我认为是有必要写一些题解来加深一点印象。
因为自己在做这道题的时候,也是有点懵。
思路(1):
dp(最长上升子序列):
用这个方法呢,是没有问题的,时间复杂度是(N*logn)。因为题目的要求是:
求ai的子序列bi的最长长度。
所以就是可以用 最 长 上 升 子 序 列 。 \color{#FF3030}{最长上升子序列。} 最长上升子序列。
即状态转移方程
f[i]=max{
f[j]+1}且j<i且b[i]&b[j]!=0
但是!注意!
这道题因为你想要AC的话必须要用位运算!
为什么?!那就看下面
思路(2):
二进制优化 (我也不知道到底应该叫什么。):
因为答案只能是由两个在二进制表示下至少有一位同是1的a序列里的数&得到的,最后求子序列的个数f[i]存的是对于a序列中当前遍历到的数中有几个在二进制表示下第i位为1,因为求的是子序列的个数而非方案,所以发现当前的数可以与之前的数&之后得到1满足题目要求,答案就只+1令dp[i]表示数列到目前为止最后一项第i位为1的最大子序列长度,每读入一个数时就
大力转移。一个数可以被它所有的二进制位的dp值转移,然后把它转移到它的所有二进制位的dp值上。
有的同学要问了,怎样求某个数二进制的第i位咧?这时候位运算就闪亮登场了。x&(1<<i)即可求出。
我们来举个栗子:
1 2 3.
转为二进制为:01 10 11
0.f[0]=0 f[1]=0;
1.f[0]=1 f[1]=0;
2.f[0]=1 f[1]=1;
3.f[0]=2 f[1]=2;
ans=2;
1 3 2 4
转为二进制为:001 011 010 100
0:f[0]=0 f[1]=0 f[2]=0
1:f[0]=1 f[1]=0 f[2]=0
2:f[0]=2 f[1]=max(f[1]+1=1,f[0]=2)=2,f[2]=0
3:f[0]=2 f[1]=3 f[2]=0
4:f[0]=2 f[1]=3 f[2]=1
ans=3
20分代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <cmath>
using namespace std;
const int maxn = 103, INF = 0x7f7f7f7f;//乱开的数组额...反正只有二十
int a[maxn], f[maxn];
int n,ans = -INF;
int main()
{
scanf("%d", &n);
for(int i=1; i<=n; i++)
{
scanf("%d", &a[i]);
f[i] = 1;
}
for(int i=1; i<=n; i++)
for(int j=1; j<i; j++)
if(a[j] < a[i])
f[i] = max(f[i], f[j]+1);
for(int i=1; i<=n; i++)
ans = max(ans, f[i]);
printf("%d\n", ans);
return 0;
}
AC代码:
#include<cstdio>
#include <algorithm>
using namespace std;
int max(int x, int y) {
return x > y ? x : y; }
int n, dp[35];
int main() {
scanf("%d", &n);
for(int i=1;i<=n;i++){
int x;
scanf("%d", &x);
int ansx = 1;
for (int i = 0; i <= 30; ++i)
if (x & (1 << i)) {
//如果这一位是1
ansx = max(ansx, dp[i] + 1);//长度++,并取最大进行f值的转移
}
for (int i = 0; i <= 30; ++i)
if (x & (1 << i)) {
dp[i] = max(dp[i], ansx);
}
}
int ans = 0;
for (int i = 0; i <= 30; ++i) {
ans = max(ans, dp[i]);
}
printf("%d", ans);
return 0;
}
总结:这道题思路非常明显。但是我们要知道,为什么只有位运算才行,这是本题最想考你的东西。
不然就太水了