CF1969F-Card Pairing【dp】

QuantAsk發表於2024-08-10

正題

題目連結:https://www.luogu.com.cn/problem/CF1969F


題目大意

有一個長度為 \(n\) 的卡牌序列 \(a\) ,每張牌是 \(1\sim k\) 中的一個型別,你先取出序列裡的前 \(k\) 張牌,然後你每次可以選擇兩張牌打出然後再抽兩張牌,如果型別一樣就加一分。

求打完所有牌你最多能加多少分。

\(2\leq k\leq n\leq 1000\)


解題思路

考慮到手牌數量和型別數量是一樣的,所以我們如果型別沒有一樣花色的牌那麼手上肯定是所有型別的牌都一樣,所以此時我們可以確定手牌情況。

\(f_i\) 表示在抽取完第 \(i\) 張牌後我們如果手上沒有型別相同的牌,我們前面最多能拿多少分。

那麼此時我們就可以對於每個 \(f_i\) 暴力往後列舉轉移,此時我們需要先丟掉兩張牌,我們可以先不確定這兩張牌,到後面再丟。

當我們走到一個位置我們手上只有兩個牌的出現次數是偶數次,那麼如果我們之前丟掉這兩張牌就會進入到一個新的手上所有牌型別都不同的狀態,此時就可以進行一次轉移。

轉移到終點時需要根據前面的轉移狀態統計答案時有不少細節可以看程式碼。

時間複雜度:\(O(n^2)\)


code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<set>
#include<map>
using namespace std;
const int N=1100;
int n,k,a[N],v[N],f[N],c[N],answer;
set<int> z;
map<int,bool> mp;
map<int,bool>::iterator it;
void dfs(int x){
	for(int i=1;i<=k;i++)v[i]=1,c[i]=0;
	z.clear();mp.clear();
	int ans=f[x],ban=0,div=k;
	answer=max(answer,ans);
	for(int i=x+1;i<=n;i++){
		if(v[a[i]])ans++,v[a[i]]=0,div--,z.insert(a[i]);
		else v[a[i]]=1,div++,z.erase(a[i]);
		if(div==k-2){
			int x=*z.begin(),y=*z.rbegin();
			if(!mp[x*k+y-1]){
				mp[x*k+y-1]=1,ban++;
				f[i]=max(f[i],ans-2);
				if(ban==k*(k-1)/2)break;
			}
		}
		if(i==n){
			it=mp.begin();
			while(it!=mp.end()){
				int y=((*it).first)%k+1,x=((*it).first)/k;
				if(v[y]&&v[x])c[x]++,c[y]++;
				it++;
			}
			answer=max(answer,ans-2);
			for(int i=1;i<=k;i++)
				if(v[i]){
					if(c[i]<div-1){
						answer=max(answer,ans);
						break;
					}
				}
		}
	}
	return;
}
int main()
{
	memset(f,0xcf,sizeof(f));
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	int div=0;
	for(int i=1;i<=n;i++){
		if(v[a[i]])answer++,v[a[i]]=0,div--;
		else v[a[i]]=1,div++;
		if(div==k){
			f[i]=answer;
			break;
		}
	}
	for(int i=1;i<=n;i++)
		if(f[i]>=0)dfs(i);
	printf("%d\n",answer);
	return 0;
}