zoukankan      html  css  js  c++  java
  • [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;
    }
    
  • 相关阅读:
    多线程:C#.NET中使用BackgroundWorker在模态对话框中显示进度条
    通过外接程序将Outlook邮件导出成Word文档
    [轉]FusionChartsFree参数说明
    MSIL学习资源
    FastCGI Error 2147467259 (0x80004005)
    编程实现双击某个文件用指定程序打开
    Excel api Enumerations 常量
    [轉]全面认识页面设置之PageSetup 对象
    AjaxFileUploaderV2.1增加可上传多个文件
    [轉]VB.NET and C# Comparison
  • 原文地址:https://www.cnblogs.com/clockwhite/p/13406069.html
Copyright © 2011-2022 走看看