1. 程式人生 > 實用技巧 >習題: Vladik and cards(狀壓DP)

習題: 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;
}