[CF743E]Vladik and cards
题目
题解
因为所有数字的选择必须连续,也就是说,同一数字的选择越少,其要求越容易达成,并且题目要求任意两个数字的出现次数 \(c(i),c(j)\) 都必须保证 \(\mid c(i)-c(j)\mid \le 1\),我们不妨二分数字出现的最小次数 \(k\)(有少部分数字出现次数为 \(k+1\))然后对这个 \(k\) 进行合法性检查,显然,\(k\) 越大,答案序列越大。
现在问题在于如何进行合法性检查,我们定义 \(f[i][j]\) 为到第 \(i\) 位,数字出现的集合为 \(j\) 时的最大序列长度?
其实有更好的定义方法,定义 \(f[i][j]\) 为到第 \(i\) 位,数字出现的集合为 \(j\) 时,出现次数为 \(k+1\) 的数字最多的出现次数,因为我们已经固定每个数字至少出现 \(k\) 次,那么出现 \(k+1\) 次的数字越多,序列长度必然越大。
考虑如何进行转移:
我们可以将每个数字的出现位置单独储存起来,并用类似于指针的数组记录第 \(i\) 位时,数字 \(num\) 应该从哪个位置开始,因为有些位置在 \(i\) 之前,已经不能选择了,为了快速得到起始位置,我们才这样做,我们记这个数组为 \(cur\)
对于一个数字 \(t\),假设我们已经要选择它,那么显然我们要选择它的连续的 \(k\) 个位置,即起始位置为 \(pos_{cur_{t,i}}\),终止位置为 \(pos_{cur_{t,i}+k-1}\),然后我们检验第 \(i\) 位之后是否还存在 \(k\) 个 \(t\) 即可
然后,便可得到状转
其中,\(j'\) 是 \(j\) 多出 \(t\) 这个数字的集合
但是这只是一种情况,数字 \(t\) 显然有可能出现 \(k+1\) 次,我们只需再检验第 \(i\) 位之后是否还存在 \(k+1\) 个 \(t\) 之后,再用状转
注意两个式子中的细微差别,还要些细节可直接见代码
代码
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
#define rep(i,__l,__r) for(signed i=(__l),i##_end_=(__r);i<=i##_end_;++i)
#define fep(i,__l,__r) for(signed i=(__l),i##_end_=(__r);i>=i##_end_;--i)
#define erep(i,u) for(signed i=tail[u],v=e[i].to;i;i=e[i].nxt,v=e[i].to)
#define writc(a,b) fwrit(a),putchar(b)
#define mp(a,b) make_pair(a,b)
#define ft first
#define sd second
typedef long long LL;
// typedef pair<int,int> pii;
typedef unsigned long long ull;
typedef unsigned uint;
#define Endl putchar('\n')
// #define int long long
// #define int unsigned
// #define int unsigned long long
#define cg (c=getchar())
template<class T>inline void read(T& x){
char c;bool f=0;
while(cg<'0'||'9'<c)f|=(c=='-');
for(x=(c^48);'0'<=cg&&c<='9';x=(x<<1)+(x<<3)+(c^48));
if(f)x=-x;
}
template<class T>inline T read(const T sample){
T x=0;char c;bool f=0;
while(cg<'0'||'9'<c)f|=(c=='-');
for(x=(c^48);'0'<=cg&&c<='9';x=(x<<1)+(x<<3)+(c^48));
return f?-x:x;
}
template<class T>void fwrit(const T x){//just short,int and long long
if(x<0)return (void)(putchar('-'),fwrit(-x));
if(x>9)fwrit(x/10);
putchar(x%10^48);
}
template<class T>inline T Max(const T x,const T y){return x>y?x:y;}
template<class T>inline T Min(const T x,const T y){return x<y?x:y;}
template<class T>inline T fab(const T x){return x>0?x:-x;}
inline int gcd(const int a,const int b){return b?gcd(b,a%b):a;}
inline void getInv(int inv[],const int lim,const int MOD){
inv[0]=inv[1]=1;for(int i=2;i<=lim;++i)inv[i]=1ll*inv[MOD%i]*(MOD-MOD/i)%MOD;
}
inline LL mulMod(const LL a,const LL b,const LL mod){//long long multiplie_mod
return ((a*b-(LL)((long double)a/mod*b+1e-8)*mod)%mod+mod)%mod;
}
const int MAXN=1000;
const int MAXSIZE=(1<<8);
const int ALL=(1<<8)-1;
int n,a[MAXN+5];
vector<int>pos[10];
inline void Init(){
n=read(1);
rep(i,0,n-1)pos[a[i]=read(1)].push_back(i);
}
int cur[10],f[MAXN+5][MAXSIZE+5];
inline int Check(const int cnt){
memset(cur,0,sizeof cur);
memset(f,-1,sizeof f);
f[0][0]=0;
int x;
rep(i,0,n-1){
rep(j,0,ALL)if(f[i][j]!=-1){
rep(k,1,8){
if((j>>(k-1))&1)continue;//此数字已经出现过
x=cur[k]+cnt-1;
if(x>=pos[k].size())continue;//这个数字根本没那么多
f[pos[k][x]][j^(1<<(k-1))]=Max(f[pos[k][x]][j^(1<<(k-1))],f[i][j]);
if((++x)>=pos[k].size())continue;
f[pos[k][x]][j^(1<<(k-1))]=Max(f[pos[k][x]][j^(1<<(k-1))],f[i][j]+1);
}
}++cur[a[i]];//指针自动往前
}
int ret=-1;
rep(i,0,n-1)ret=Max(ret,f[i][ALL]);
if(ret==-1)return -1;
return ret*(cnt+1)+(8-ret)*cnt;
}
inline void Bisearch(){
int l=1,r=n/8,mid;
while(l+1<r){
// printf("Now l == %d, r == %d\n",l,r);
mid=l+r>>1;
if(Check(mid)!=-1)l=mid;
else r=mid-1;
}
int ans=Max(Check(l),Check(r));
if(ans==-1){
ans=0;
rep(i,1,8)if(pos[i].size())++ans;
}writc(ans,'\n');
}
signed main(){
Init();
Bisearch();
return 0;
}