CF1969F-Card Pairing【dp】
正题
题目链接: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;
}