题目梗概
给出 n 个元素的序列,每个元素的是大小不超过 8 的正整数。请找出满足下面两个条件的最长
子序列:
1. 任意两个数字出现的次数之差的绝对值不超过 1, 未出现的算 0次;
2. 相同的元素是连续的(在原始序列中可以不连续)。
解题思路
首先枚举一个t表示每个数的个数为t或t+1(这里其实可以二分)。
因为每个数选择的区间必须连续,所以可以用DP验证。
f[i][j]表示前i个数,j状态下的最大长度(j表示每个是否取过)。
转移方程显然。
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=1005,m=256;
int sum[maxn][10],n,f[maxn][300];
int max(int x,int y){if (x>y) return x;return y;}
int find(int x,int y,int L){
int R=n,mid;
if (sum[L][y]>=x) return L;
while(L<=R){
mid=L+(R-L>>1);
if (sum[mid-1][y]<x&&x<=sum[mid][y]) return mid;
if (x>sum[mid][y]) L=mid+1;else R=mid-1;
}
return -1;
}
int work(int t){
memset(f,0,sizeof(f));
f[0][0]=1;
for (int i=0;i<=n;i++)
for (int j=0;j<m;j++) if (f[i][j])
for (int k=0;k<8;k++) if (((1<<k)&j)==0){
int x1=find(sum[i][k+1]+t,k+1,i),x2=find(sum[i][k+1]+t+1,k+1,i);
if (x1!=-1) f[x1][j+(1<<k)]=max(f[x1][j+(1<<k)],f[i][j]+t);
if (x2!=-1) f[x2][j+(1<<k)]=max(f[x2][j+(1<<k)],f[i][j]+t+1);
}
int ans=0;
for (int i=1;i<=n;i++) ans=max(ans,f[i][m-1]);
return ans-1;
}
int main(){
freopen("longseq.in","r",stdin);
freopen("longseq.out","w",stdout);
scanf("%d",&n);
for (int i=1,x;i<=n;i++){
scanf("%d",&x);
for (int j=1;j<=8;j++) sum[i][j]=sum[i-1][j]+(j==x);
}
for (int i=n/8;i>=0;i--){
int x=work(i);
if (x!=-1) return printf("%d",x),0;
}
}