题目描述
难度分:1739
输入n(1≤n≤2×105)和长为n的数组a(1≤a[i]≤20)。
输出a的子序列b的最长长度,满足:
- b的长度是偶数。
- b[0]=b[1],b[2]=b[3],……即两个两个一组,每组中的元素都相同。
- b中的每种数字恰好在b中出现两次。
注:子序列不一定连续。
输入样例1
7
1 3 3 1 2 2 1
输出样例1
4
输入样例2
1
20
输出样例2
0
算法
状压DP
这个题的值域非常小,比较容易想到状压DP
,但是220已经很大了,如果状态再加上下标的话就会有O(220n)的状态,是不可能存下来的。
状态定义
dp[mask]表示状态mask达成时,这个模式最后一个数字在原数组中的最左索引。因为对于同一个模式,越靠左越能为后面腾出更多位置,让后面有可能接更多元素。先将a数组中的所有元素都减去1,mask中第i位是1就表示数子i存在于子序列中。如果S是所有可以达成的状态集合,那么答案就是maxmask∈Spopcount(mask)×2,其中popcount(x)表示二进制数字x中1的个数。
状态转移
首先dp[0]=0,表示空序列的最后一个位置是索引0。那么对于一个状态mask,我们考虑接下来将不存在于mask中的数字v∈[0,M]加入到序列末尾,其中M位最大值。
先得到mask状态最后一位i=dp[mask],然后找i右边最近的v,假设在j=R[i][v]位置,如果j=R[i][v]≤n,且k=R[R[i][v]][v]≤v,说明模式mask后面可以找到两个v,v可以接在模式mask的后面。
其中R[i][v]表示i右边最近的v,可以倒序遍历一遍a直接预处理出来。然后我们维护dp[mask∨2v]=min(dp[mask∨2v],k)的最小值即可,如果mask可以达成,就维护popcount(mask)×2的最大值。
复杂度分析
时间复杂度
遍历一遍数组a,对每个i,遍历v∈[0,m]就能处理出R数组,时间复杂度为O(nm)。遍历所有状态mask的时间复杂度为O(2m),对于每个状态,需要枚举接在后面的数字v,时间复杂度为O(m),本题中m=20,所以DP
的时间复杂度为O(m2m)。整个算法的时间复杂度为O(m(n+2m))。
空间复杂度
空间瓶颈在于R数组和DP
数组,两者的空间复杂度分别为O(nm)和O(2m),整个算法的额外空间复杂度为O(nm+2m)。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
const int N = 200010, M = 20;
int n, a[N], dp[(1<<M) + 5], nxt[M], R[N][M];
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
a[i]--;
}
memset(nxt, 0x3f, sizeof nxt);
for(int i = n; i >= 0; i--) {
for(int v = 0; v < M; v++) {
R[i][v] = nxt[v]; // i右边最近的v在哪个索引
}
nxt[a[i]] = i;
}
int ans = 0;
memset(dp, 0x3f, sizeof dp);
dp[0] = 0;
int cnt = 0;
for(int mask = 0; mask < (1<<M); mask++) {
int i = dp[mask];
if(i > n) continue; // 这个状态达成不了
for(int v = 0; v < M; v++) {
if(mask>>v&1) continue;
int j = R[i][v];
if(j > n) continue;
int k = R[j][v];
if(k <= n) {
dp[mask|(1<<v)] = min(dp[mask|(1<<v)], k);
}
}
ans = max(ans, __builtin_popcount(mask)<<1);
}
printf("%d\n", ans);
return 0;
}