题解:绝世好题

洛谷P4310

Posted by TH911 on January 18, 2025

题目传送门

回顾:最长子序列问题

先让我们回想一下最长上升子序列是如何解决的。

定义 $dp[i]$ 表示以 $a[i]$ 结尾的最长子序列的长度。

那么 $dp[i]$ 就是在 $[1,i-1]$ 中找到一个满足 $a[j]<a[i]$ 且 $dp[j]$ 最大的 $j$,则 $dp[i]=dp[j]+1$。

这是 $\mathcal O(n^2)$ 的做法。


对于 $\mathcal O(n\log n)$ 做法,则是令 $dp[i]$ 表示长度为 $i$ 的子序列的最后一位,然后每次加入的时候就在 $dp$ 上二分加入,最后查询 $i$ 的最大值即可。

最长上升子序列的推广

现在来思考本题。

$\mathcal O(n^2)$ 做法

仍然定义 $dp[i]$ 表示以 $a[i]$ 结尾的满足条件的最长子序列的长度。

一个 $\mathcal O(n^2)$ 的做法是,$i$ 枚举 $[1,n]$,$j$ 枚举 $[1,i-1]$,若 $a[i]\ \&\ a[j]>0$,则有:

\[dp[i]\leftarrow \max(dp[i],dp[j])\]

此时再有 $dp[i]\leftarrow dp[i]+1$,最终答案即 $\max\limits_{i=1}^ndp[i]$。

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath> 
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
const int N=100000;
int n,a[N+1],dp[N+1];
int main(){
	/*freopen("test.in","r",stdin);
	freopen("test.out","w",stdout);*/
	
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d",a+i);
	}
	int Max=0; 
	for(int i=1;i<=n;i++){
		for(int j=1;j<i;j++){
			if(a[i]&a[j]){
				dp[i]=max(dp[i],dp[j]);
			}
		}dp[i]++;
		Max=max(Max,dp[i]);
	}printf("%d\n",Max);
	
	/*fclose(stdin);
	fclose(stdout);*/
	return 0;
}

期望得分:$30\text{pts}$。

实际得分:$90\text{pts}$。

数据严重过水。

$\mathcal O(n\log n)$ 做法

我们可以尝试优化我们的做法。

回顾最长上升子序列问题,发现之所以能够优化,是因为二分的存在。

然而本题显然无法二分,考虑其他做法。

分析题目条件,序列元素之间只要有一个二进制位都是 $1$,就可以分到一组。

令 $dp[i]$ 表示末尾元素二进制位上权值为 $2^i$ 的位为 $1$ 的子序列的最长长度。

那么就看所有被 $a[i]$ 包含的 $2^j$ 的 $dp[j]$,取最大值后加 $1$ 可以得到以 $a[i]$ 结尾的最长子序列长度 $s$。此时我们又可以使用 $s$ 将这些 $dp[j]$ 全部更新。

最终答案即 $\max\limits_{i=1}^{\log V}dp[i]$。

AC 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath> 
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
int n,x,dp[30];
int main(){
//	freopen("test.in","r",stdin);
//	freopen("test.out","w",stdout);
	
	scanf("%d",&n);
	int ans=0; 
	for(int i=1;i<=n;i++){
		scanf("%d",&x); 
		int Max=0;
		for(int j=0;j<30;j++){
			if(x&(1<<j))Max=max(dp[j],Max);
		}Max++;
		for(int j=0;j<30;j++){
			if(x&(1<<j))dp[j]=max(dp[j],Max);
			ans=max(ans,dp[j]);
		}
	}printf("%d\n",ans);
	
	/*fclose(stdin);
	fclose(stdout);*/
	return 0;
}