【CCPC】2022绵阳站 A. Ban or Pick, What's the Trick | 对抗搜索、记忆化

235 阅读3分钟

本文已参与「新人创作礼」活动, 一起开启掘金创作之路。

【CCPC】2022绵阳站 A. Ban or Pick, What's the Trick | 对抗搜索、记忆化

题目链接

Problem - A - Codeforces

题目

image.png

题目大意

给定两个长度为 nn 的数组 a1,a2,...,ana_1,a_2,...,a_nb1,b2,...,bnb_1,b_2,...,b_n

有两个人 A 和 B 玩游戏。共举行 nn 轮游戏,每轮游戏 A 和 B 分别进行一次操作。即他们分别都会操作 nn 次,共操作 2×n2\times n 次。

每轮游戏 A 可以选择进行以下两种操作中的一种:

  1. 选择 aa 数组中的一个数字并拿在手中。
  2. 选择 bb 数组中的一个数字并扔掉。

B 可以选择进行以下两种操作中的一种:

  1. 选择 bb 数组中的一个数字并拿在手中。
  2. 选择 aa 数组中的一个数字并扔掉。

容易发现每个数字会且只会被操作一次。A 手中最多持有 kk 个数字,B 手中最多也只能持有 kk 个数字。

令 A 手中所有数字的和为 sAs_A,B 手中的所有数字和为 sBs_B。A 希望最大化 sAsBs_A-s_B,B 希望最小化 sAsBs_A-s_B。二人都使用最优策略的情况下求最终的 sAsBs_A-s_B

1n105,1k101\le n \le 10^5,1\le k\le 10

思路

容易发现每次操作要么将己方数组中最大的元素拿走,要么把对方数组里最大的元素扔掉,不可能对非最大元素进行操作。先对 aa 数组和 bb 数组进行排序。

那么如果我们知道现在在进行第 tt 轮游戏,A 手中有 tata 张牌,B 手中有 tbtb 张牌,我们就可以得知:

  • 如果轮到 A 操作,B 已经操作了 t1t-1次,他扔掉了 aa 数组中 t1tbt-1-tb 张牌。所以 A 要么把 a(t1tb)+ta+1a_{(t-1-tb)+ta+1} 拿走,要么把 b(t1ta)+tb+1b_{(t-1-ta)+tb+1} 扔掉。
  • 同理,如果轮到 B 操作,B 要么把 b(tta)+tb+1b_{(t-ta)+tb+1} 拿走,要么把 a(t1tb)+ta+1a_{(t-1-tb)+ta+1} 扔掉。

所以我们只需要记录上述三个信息就可以进行转移。

solveA(t,ta,tb)solveA(t,ta,tb) 表示 A 从第 tt 轮到第 nn 轮里 A 能得到的最优解,solveB(t,ta,tb)solveB(t,ta,tb) 表示 B 从第 tt 轮到第 nn 轮里 B 能得到的最优解。那么显然有:

  • solveA(t,ta,tb)=max(solveB(t,ta,tb)+a(t1tb)+ta+1,solveB(t,ta,tb))solveA(t,ta,tb)=max(solveB(t,ta,tb)+a_{(t-1-tb)+ta+1},solveB(t,ta,tb))
  • solveB(t,ta,tb)=min(solveA(t,ta,tb)+b(tta)+tb+1,solveA(t,ta,tb))solveB(t,ta,tb)=min(solveA(t,ta,tb)+b_{(t-ta)+tb+1},solveA(t,ta,tb))

一些边界问题的处理参见代码。

直接搜索显然会 TLE,但是我们可以开个数组 dpAdpAdpBdpB 记录搜索的结果,则搜索的过程相当于填充 dpdp 数组,时间复杂度变得可以接受。

注意 solvesolve 函数的函数值里有很多 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;
}