[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的主要内容,如果未能解决你的问题,请参考以下文章

CodeForces743E. Vladik and cards 二分+装压dp

CF1172A Nauuo and Cards 贪心

题解 CF546C Soldier and Cards

CF 1173C Nauuo and Cards

CF815DKaren and Cards 单调栈+扫描线

Vladik and fractions