习题: Vladik and cards(状压DP)

题目

传送门

思路

比较显然的一点,这道题对于一个方案,所有出现的数字的最小出现次数是有单调性的

考虑二分出所有出现数字的最小出现次数

\(dp[i][j]\)表示前i个数,已经达到目标状态的数的状态为j

因为题目中要求数是连续的

所以我们只需要考虑接下来的一段全部是哪一个数字即可,用这一点进行转移

最后考虑是否有一个\(dp[i][(1<<8)-1]\)合法即可

代码

#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
int n;
int a[1005];
int l,r,mid,ans;
int t[1005];
int dp[1005][(1<<8)];
vector<int> pos[1005];
int check(int cnt)
{
	memset(t,0,sizeof(t));
	memset(dp,-0x3f,sizeof(dp));
	int bas=dp[0][0];
	dp[1][0]=0;
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<(1<<8);j++)
		{
			if(bas!=dp[i][j])
			{
				for(int k=0;k<8;k++)
				{
					if(!(j&(1<<k)))
					{
						int nxt=cnt+t[k]-1;
						if(nxt>=pos[k].size())
							continue;
						dp[pos[k][nxt]][j|(1<<k)]=max(dp[pos[k][nxt]][j|(1<<k)],dp[i][j]);
						nxt++;
						if(nxt>=pos[k].size())
							continue;
						dp[pos[k][nxt]][j|(1<<k)]=max(dp[pos[k][nxt]][j|(1<<k)],dp[i][j]+1);
					}
				}
			}
		}
		t[a[i]-1]++;
	}
	int ret=-1;
	for(int i=1;i<=n+1;i++)
		ret=max(ret,dp[i][(1<<8)-1]);
	return ret==-1?-1:ret+cnt*8;
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
		pos[a[i]-1].push_back(i);
	}
	l=0;
	r=n*8;
	while(l+1<r)
	{
		mid=(l+r)>>1;
		int t=check(mid);
		if(t!=-1)
		{
			ans=max(ans,t);
			l=mid;
		}
		else
			r=mid;
	}
	//cout<<"end";
	while(check(l+1)!=-1)
	{
		//cout<<l+1<<' '<<check(l+1)<<endl;
		ans=max(ans,check(l+1));
		l++;
	}
	if(ans==0)
	{
		for(int i=0;i<8;i++)
			if(pos[i].size())
				ans++;
	}
	cout<<ans;
	return 0;
}
posted @ 2020-08-09 15:34  loney_s  阅读(173)  评论(0)    收藏  举报