本文已参与「新人创作礼」活动, 一起开启掘金创作之路。
【CCPC】2022绵阳站 A. Ban or Pick, What's the Trick | 对抗搜索、记忆化
题目链接
题目
题目大意
给定两个长度为 的数组 和 。
有两个人 A 和 B 玩游戏。共举行 轮游戏,每轮游戏 A 和 B 分别进行一次操作。即他们分别都会操作 次,共操作 次。
每轮游戏 A 可以选择进行以下两种操作中的一种:
- 选择 数组中的一个数字并拿在手中。
- 选择 数组中的一个数字并扔掉。
B 可以选择进行以下两种操作中的一种:
- 选择 数组中的一个数字并拿在手中。
- 选择 数组中的一个数字并扔掉。
容易发现每个数字会且只会被操作一次。A 手中最多持有 个数字,B 手中最多也只能持有 个数字。
令 A 手中所有数字的和为 ,B 手中的所有数字和为 。A 希望最大化 ,B 希望最小化 。二人都使用最优策略的情况下求最终的 。
。
思路
容易发现每次操作要么将己方数组中最大的元素拿走,要么把对方数组里最大的元素扔掉,不可能对非最大元素进行操作。先对 数组和 数组进行排序。
那么如果我们知道现在在进行第 轮游戏,A 手中有 张牌,B 手中有 张牌,我们就可以得知:
- 如果轮到 A 操作,B 已经操作了 次,他扔掉了 数组中 张牌。所以 A 要么把 拿走,要么把 扔掉。
- 同理,如果轮到 B 操作,B 要么把 拿走,要么把 扔掉。
所以我们只需要记录上述三个信息就可以进行转移。
令 表示 A 从第 轮到第 轮里 A 能得到的最优解, 表示 B 从第 轮到第 轮里 B 能得到的最优解。那么显然有:
一些边界问题的处理参见代码。
直接搜索显然会 TLE,但是我们可以开个数组 和 记录搜索的结果,则搜索的过程相当于填充 数组,时间复杂度变得可以接受。
注意 函数的函数值里有很多 0,常见写法判断记录数组值是否为 0 来返回存储的答案并不适用。
代码
#include <bits/stdc++.h>
using namespace std;
using LL=long long;
const int N=100001;
int n,m;
LL a[N],b[N];
bool visB[N][11][11],visA[N][11][11];
LL dpA[N][11][11];
LL dpB[N][11][11];
int cmp(LL a,LL b)
{
return a>b;
}
LL solveA(int,int,int);
LL solveB(int,int,int);
LL solveA(int t,int ta,int tb)
{
if (t>n) return 0;
if (visA[t][ta][tb]) return dpA[t][ta][tb];
visA[t][ta][tb]=1;
if (ta==m||t-tb+ta>n) return dpA[t][ta][tb]=solveB(t,ta,tb);
return dpA[t][ta][tb]=max(solveB(t,ta+1,tb)+a[t-tb+ta],solveB(t,ta,tb));
}
LL solveB(int t,int ta,int tb)
{
if (visB[t][ta][tb]) return dpB[t][ta][tb];
visB[t][ta][tb]=1;
if (tb==m||t-ta+tb+1>n) return dpB[t][ta][tb]=solveA(t+1,ta,tb);
return dpB[t][ta][tb]=min(solveA(t+1,ta,tb+1)-b[t-ta+tb+1],solveA(t+1,ta,tb));
}
int main()
{
scanf("%d%d",&n,&m);
m=min(n,m);
for (int i=1;i<=n;++i) scanf("%lld",&a[i]);
for (int i=1;i<=n;++i) scanf("%lld",&b[i]);
sort(a+1,a+n+1,cmp);
sort(b+1,b+n+1,cmp);
cout<<solveA(1,0,0)<<endl;
return 0;
}