题意:给一串取值为1-8的序列,找出满足下列要求的最长子序列(不要求连续):
1.每个值出现的次数相差不超过1;
2.子序列中相同的值得出现在一起。
设满足要求的子序列数字出现次数至少为len。
性质:
1.子序列中出现次数至少为len,且有t个值出现len+1次,则长度为len*8+t;
2.若存在满足最少出现次数为len的子序列,则一定存在len-1的子序列(所有取值均丢掉一个即可)。
解法:通过性质二,可以想到二分len。
check函数:
check(len):是否存在满足要求的子序列,且若存在,返回子序列的最大长度。
最大长度:若存在满足要求的子序列(最小出现次数为len),则应该使出现次数为len+1的数字尽量多,定义数量为T,可用状态压缩dp求T的最大值。
做法:dp[i][j]:0<=i<=n,0<=j<=(1<<8),含义:原串扫到位置i时,集合K中出现次数为len+1的数字个数的最大值。集合K为j中为1的位所代表数字组成的集合。
初始化dp[0][0]=0,代表存在满足要求的长度为0的子序列。其他为-inf,代表不存在。
转移方程:
见代码。
cur数组维护当前位置已经出现过的数字的个数。记得在每次二分的时候初始化一下。
vec数组维护每个数字出现的每个位置,按从小到大有序排列。
由于check函数写法的原因,需要在最后特判一下len为0的情况。
代码如下:
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
typedef unsigned int uii;
const int maxn=1005;
const int inf=0x3f3f3f3f;
const int s=1<<8;
int n,a[maxn],dp[maxn][s],cur[maxn];
vector<int> vec[maxn];
int check(int len) {
memset(cur,0,sizeof cur);
for (int i=0;i<=n;++i)
for (int j=0;j<s;++j)
dp[i][j]=-inf;
dp[0][0]=0;
for (int i=0;i<n;++i) {
for (int j=0;j<s;++j) {
if (dp[i][j]==-inf)
continue;
for (int k=0;k<8;++k) {
if (j&(1<<k))
continue;
uii t=len+cur[k]-1;
if (t>=vec[k].size())
continue;
dp[vec[k][t]+1][j|(1<<k)]=max(dp[vec[k][t]+1][j|(1<<k)],dp[i][j]);
++t;
if (t>=vec[k].size())
continue;
dp[vec[k][t]+1][j|(1<<k)]=max(dp[vec[k][t]+1][j|(1<<k)],dp[i][j]+1);
}
}
++cur[a[i]];
}
int ret=-inf;
for (int i=0;i<=n;++i)
ret=max(ret,dp[i][s-1]);
return ret==-inf?-1:len*8+ret;
}
int main()
{
scanf("%d",&n);
for (int i=0;i<n;++i) {
scanf("%d",&a[i]);
--a[i];
vec[a[i]].push_back(i);
}
int l=1,r=n>>3,r1=0,res=0;
while (l<=r) {
int mid=(l+r)>>1;
r1=check(mid);
if (r1!=-1) {
res=max(res,r1);
l=mid+1;
} else
r=mid-1;
}
if (res==0)
for (int i=0;i<8;++i)
if (vec[i].size())
++res;
printf("%d\n",res);
return 0;
}