题目链接
:P9911 [COCI 2023/2024 #2] Kuglice
题目大意
有
n
n
n 个带有颜色的球,每轮两个人各可以从一端拿一个球
,最后求两个人手中不同颜色球
的比值
第一行一个整数 n n n
第二行 n n n 个整数 a [ 1 a[1 a[1 ~ n ] n] n] 表示从左到右每个球的颜色
。
思路
显然,我们可以想到这是一个博弈dp
例如可以定义
d
p
[
l
]
[
r
]
[
o
p
]
dp[l][r][op]
dp[l][r][op] 表示在
l
l
l 到
r
r
r 的区间内(
o
p
op
op 表示先后手
)可以取到多少个不同颜色
的球
但动态转移方程式
是什么呢?
其实可以由
l
+
1
l+1
l+1 到
r
r
r 的区间加上第
l
l
l 个数 或
l
l
l 到
r
−
1
r-1
r−1 的区间加上第
r
r
r 个数 得到
即
d
p
[
l
]
[
r
]
[
o
p
]
=
m
a
x
(
d
p
[
l
−
1
]
[
r
]
[
o
p
]
+
dp[l][r][op]=max(dp[l-1][r][op]+
dp[l][r][op]=max(dp[l−1][r][op]+ (取第
l
l
l 个数可不可以加1)
,
d
p
[
l
]
[
r
−
1
]
[
o
p
]
+
, dp[l][r-1][op]+
,dp[l][r−1][op]+ (取第
r
r
r 个数可不可以加1)
)
)
)
所以再加一个记忆化搜索
就行了
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n,a[3005],ans,sum;
int lt[3005],rt[3005]; //lt[x]=y 表示左边第一个x在y的位置,rt[]同理
int dp[3005][3005][2]; //dp[l][r][op]=x 表示op这个人在l到r的区间内,可以得x分 //op=0先手,op=1后手
int f(int l,int r,int x){ //根据左边和右边第一个x的位置判断x是否可以加1个
return (x==lt[a[x]] && r>=rt[a[x]]) || (x==rt[a[x]] && l<=lt[a[x]]);
//如果正好是左数第一个且右边第一个没有选到,那么选了可以加1个,右边同理
}
void dfs(int l,int r,int op){
if(dp[l][r][op]!=-1) return ; //如果已经搜索过了就退出
if(l==r)
{
dp[l][r][op]=f(l,r,l),dp[l][r][op^1]=0; //如果l=r,那么就看a[l]可以可以加1个
return ;
}
dfs(l+1,r,op^1),dfs(l,r-1,op^1); //往里搜索,先后手交换
ans=dp[l+1][r][op]+f(l,r,l); //由l+1到r的区间选a[l]转移得到
sum=dp[l][r-1][op]+f(l,r,r); //由l到r-1的区间选a[r]转移得到
if(ans>sum) dp[l][r][op]=ans,dp[l][r][op^1]=dp[l+1][r][op^1]; //如果由l+1到r的区间转移过来:那么它的对手也由l+1到r的区间转移过来
else if(ans<sum) dp[l][r][op]=sum,dp[l][r][op^1]=dp[l][r-1][op^1]; //同上一行
else dp[l][r][op]=ans,dp[l][r][op^1]=min(dp[l+1][r][op^1],dp[l][r-1][op^1]); //如果一样的,那么就让对手选少的一项
return ;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
if(lt[a[i]]==0) lt[a[i]]=i; //如果这是左边第一个a[i],那么记录第一个a[i]在i上
rt[a[i]]=i; //右边第一个a[i]更新为在i上
}
memset(dp,-1,sizeof dp); //把dp数组赋值为-1,方便记忆化
dfs(1,n,0);
printf("%d:%d",dp[1][n][0],dp[1][n][1]); //输出
return 0;
}// \(^_^)/ 完结撒花!