E. Vladik and cards Codeforces Round #384 (Div. 2) 好题 二分+(贪心+状态压缩DP)判断

题意:给一串取值为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;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值