[CF743E] Vladik and cards
Posted clockwhite
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[CF743E] Vladik and cards相关的知识,希望对你有一定的参考价值。
[CF743E] Vladik and cards
一.前言
? 把子序列看成子串还真是对不起了。题目链接
二.思路
? 首先由每两个数字出现的次数之差不超过1可以知道,以下几点。对于一个可以记入答案的序列,有
- 所有的数字都在里面(除非部分数字只选一个,其余不选)
- 所有出现的数字的出现次数之中有一个最小值 k
- 有部分数字的出现次数为 k+1
然后我们试着看一看k和答案的关系,假如设 add 为出现次数为 k+1 的数字个数,那么很显然的有 (ans=8*k+add).并且有 ans和k成正相关的关系。
? 有了以上的结论之后,我们可以二分猜k,然后试图计算出在完成每个数字至少出现k个的标准时,add的值。若是无法达到标准,那么就将k下调,否则上升。
? 现在只需要解决如何求add的问题。这里使用状压DP,(f[x][j])表示在x的位置 j 所代表的数字都已经出现至少 k 次的add。然后需要一个辅助变量,(pos[x][i])表示序列中第 i 个 x 的位置。这里用了一小点贪心的思想,即如果我们要选一些相同的数出来,那么这些数肯定是靠的越近越好,这样才能为后面的DP提供更多的机会。
? 那么转移方程可以比较清楚的推出,这里写不清楚,还是看代码会比较容易。需要注意的是,每次进行转移的时候,我们是先选出状态以外的一个数 p,然后得出这个 p 之后第 k-1 的 p的位置,转移到那上面去。(这样总共就有k个p),然后如果后面还剩的有 k 个 p(即总可塞入的有k+1个)那么也转移一下。
? 如何得到之后第 k-1 个 p 的位置是依靠于pos数组,但是每次不可能从(pos[p][1]) 开始往后面算,那么再用一个 start 数组表示应该是从 (pos[p][start[p]]) 开始找 k-1 个,即求出 (pos[p][start[p]+k-1]),注意(start)随着i而变化。
? 最后初始化(f[0][0]=0)就可以轻松解决掉这道题啦!
三.CODE
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<fstream>
#include<cmath>
#include<cstring>
using namespace std;
int read(){
char ch=getchar();
int res=0,f=1;
for(;ch<‘0‘||ch>‘9‘;ch=getchar())if(ch==‘-‘)f=-1;
for(;ch>=‘0‘&&ch<=‘9‘;ch=getchar())res=res*10+(ch-‘0‘);
return res*f;
}
const int MAXN=(1<<8)-1;
int n,a[1005],t[9],pos[9][1005],r=1<<15,l;
int start[9],f[1005][MAXN+5],ans;
inline int maxx(int x,int y){
return x>y?x:y;
}
bool check(int x){
for(int i=1;i<=8;++i)start[i]=1;//初始化
memset(f,250,sizeof(f));
f[0][0]=0;
for(int i=0;i<n;++i){
for(int j=0;j<MAXN;++j)
if(f[i][j]>=0)//用它去更新
for(int k=1;k<=8;++k){//选一个数
if(j&(1<<(k-1)))continue;
int u=start[k]+x-1,g=j|(1<<(k-1));
if(u<=t[k])f[pos[k][u]][g]=maxx(f[pos[k][u]][g],f[i][j]);
if(++u<=t[k])f[pos[k][u]][g]=maxx(f[pos[k][u]][g],f[i][j]+1);//可以选出x+1个
}
++start[a[i]];//更新,每次选的数都要在i及其以后
}
int add=-1;
for(int i=8;i<=n;++i)add=maxx(add,f[i][MAXN]);//前7个不可能有答案
if(add==-1)return 0;
ans=maxx(ans,8*x+add);
return 1;
}
int main(){
n=read();
for(int i=1;i<=n;++i){
a[i]=read();
pos[a[i]][++t[a[i]]]=i;//记录位置
}
for(int i=1;i<=8;++i)r=min(r,t[i]);
if(r==0){//特判是否有数字没有,那么就是括号内的特殊情况
int res=0;
for(int i=1;i<=8;++i)if(t[i])res++;
cout<<res;
return 0;
}
while(l<=r){//二分猜k
int mid=(l+r)>>1;
if(check(mid))l=mid+1;
else r=mid-1;
}
printf("%d",ans);
return 0;
}
以上是关于[CF743E] Vladik and cards的主要内容,如果未能解决你的问题,请参考以下文章