bzoj4300 绝世好题 【dp】By cellur925

题目描述:

给定一个长度为\(n\)的数列\(a\),求\(a\)的子序列\(b\)的最长长度,满足bi&bi-1!=0(\(2<=i<=len\))。

90分做法:

并没有部分分,但是我们可以很容易地想出\(O(n^2)\)算法:诸如最长上升子序列。

但是一定要注意位运算需要大力加括号,就算是与运算!!(因为这个WA了好几次hhh)

#include<cstdio>
#include<algorithm>

using namespace std;

int n,ans;
int a[100090],f[100090];

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    for(int i=1;i<=n;i++)
    {
        f[i]=1;
        for(int j=1;j<i;j++)
            if((a[i]&a[j])!=0) f[i]=max(f[i],f[j]+1);
        ans=max(ans,f[i]);
    }
    printf("%d\n",ans);
    return 0;
}

AC做法:

其实我们并不需要枚举由谁转移过来。首先这是位运算,我们要有点经验,这个情况一定是按位枚举的,状态量不会很大(\(30\)左右?)我们从位的角度出发:

因为是要求与运算不为0,那么两个数的二进制表示一定存在一位使得两个数的这位都为1.

\(f[i]\)为数列到目前为止最后一项第\(i\)位为1最长子序列长度,对于每一个新数,我们用它来找到一个它为结尾的最长长度,再用这个最长长度来更新其他答案。

#include<cstdio>
#include<algorithm>

using namespace std;

int n,ans;
int a[100090],f[100090];

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		int qwq=0;
		for(int j=0;j<=30;j++) 
			if(a[i]&(1<<j)) qwq=max(qwq,f[j]+1);
		for(int j=0;j<=30;j++)
			if(a[i]&(1<<j)) f[j]=max(f[j],qwq); 
	}
	for(int i=0;i<=30;i++)
		ans=max(ans,f[i]);
	printf("%d\n",ans);
	return 0;
}

1.和位运算有关的dp从位的角度出发

2.位运算大力加括号。

posted @ 2018-10-30 21:14  cellur925&Chemist  阅读(116)  评论(0编辑  收藏  举报