【ARC116F】Deque Game 题解

AtC 传送门:ARC116F Deque Game

找规律。

Solution

手玩样例极其重要。

1

通过手玩小样例可以发现:

  • 若数列长度 n n n 为奇数( m = n + 1 2 m=\frac{n+1}{2} m=2n+1):高桥先手,答案为 min ⁡ ( A m , max ⁡ ( A m − 1 , A m + 1 ) ) \min(A_m,\max(A_{m-1},A_{m+1})) min(Am,max(Am1,Am+1));若青木先手,答案为 max ⁡ ( A m , min ⁡ ( A m − 1 , A m + 1 ) ) \max(A_m,\min(A_{m-1},A_{m+1})) max(Am,min(Am1,Am+1))
  • 反之( m = n 2 m=\frac{n}{2} m=2n):高桥先手,答案为 max ⁡ ( A m , A m + 1 ) \max(A_m,A_{m+1}) max(Am,Am+1);青木先手,答案为 min ⁡ ( A m , A m + 1 ) \min(A_m,A_{m+1}) min(Am,Am+1)

想要详细严谨的推论?

由此,我们得到(手玩样例也可以得到):

  • 若序列长度为奇数,则每个人都希望自己是后手;
  • 若序列长度为偶数,则每个人都希望自己是先手。

综上,我们知道:如果一个长度为奇数的序列,已知操作它的先手是谁,那么就可以直接得到它最后剩余的那个数。

因为偶长序列先手更优,所以两人应在开始时去抢偶长序列,让自己成为某些偶长序列的先手,使得自己在这些偶长序列中收益最大。

2

所以,他们的行动方式应该是:

  1. 两个人轮流 在当前局面内 长度为偶数的序列中 挑选一个序列取走其首项或末项,将它改为一个长度为奇数的序列。

    而被挑选的序列满足:假设一个偶长序列先取走首项后剩下的值为 x x x,先取末项的剩余值是 y y y,则有 m n = min ⁡ ( x , y ) ,   m x = max ⁡ ( x , y ) mn = \min(x,y),\ mx = \max(x,y) mn=min(x,y), mx=max(x,y)。那么这个被取序列一定是当前所有的长度为偶数的序列中, ( m x − m n ) (mx-mn) (mxmn) 的值最大的一个偶长序列。

    为什么这样能保证最优?对高桥而言,若取它,则会比青木取它让结果总值多出 ( m x − m n ) (mx - mn) (mxmn) 个值;对青木而言,若取它,则会比高桥取它让结果总值少掉 ( m x − m n ) (mx-mn) (mxmn) 个值。

  2. 当所有偶长序列都被更改为奇长序列之后,接下来没有偶长序列可取的人不得不成为剩下所有奇长序列的先手。又因为此时所有的奇长序列的剩余值都可以提前预处理出(对于每个奇长序列分别有两种预处理情况:高桥先手和青木先手)。故最后相加先手所对应的答案即可。

Code

贴上 @fjy666 大佬的详细注释代码。

#include <bits/stdc++.h>
using namespace std;
#define _rep(i_,a_,b_) for(int i_ = a_; i_ <= b_; ++i_)
#define mid ((L+R) >> 1)
#define get(x) (((x) - 1) / kb + 1)
#define multiCase() int testCnt = in(); _rep(curCase,1,testCnt)
typedef long long ll;

int in(void) { int x; scanf("%d", &x); return x; }
ll inl(void) { ll x; scanf("%lld", &x); return x; }
template<typename T> void chkmax(T &a, const T &b) { a = max(a, b); } 
template<typename T> void chkmin(T &a, const T &b) { a = min(a, b); } 
const int kN = 200500;
int a[kN], n;

//以下使用 0 代表 MINer, 1 代表 MAXer
//返回如果操作区间为 [L,R],先操作者为 opt,最后会剩下啥
int f(int L, int R, int opt) { 
	if(L == R) return a[L]; //如果长度为1那么谁也不能拿了

	if((R - L + 1) & 1) //奇数区间
		return opt ? 
	 		   max(f(L, R - 1, opt ^ 1), f(L + 1, R, opt ^ 1)) 
			  :min(f(L, R - 1, opt ^ 1), f(L + 1, R, opt ^ 1));
	else return opt ? max(a[mid], a[mid + 1]) : min(a[mid], a[mid + 1]); 
}

int evcnt;
struct Game { //一局长为偶数的游戏
	int mx, mn;
	//mx:如果MAXer先动手改成 ODD 留下的数目
	//mn:如果MINer先动手改成 ODD 留下的数目

	void init(int mxer, int mner) {
		mx = mxer, mn = mner;
		if(mx < mn) swap(mx, mn); //MAXer希望答案最大
	}
	bool operator > (const Game &rhs) const {
		return mx - mn > rhs.mx - rhs.mn; //按照差值来排序
	}
} EVEN[2][kN];
//EVEN[0][j].mx 代表如果最后是 MINer 先取 已经被 MAXer 改成奇数 的这堆,能取到多少
//EVEN[0][j].mn 代表如果最后是 MINer 先取 已经被 MINer 改成奇数 的这堆,能取到多少
//EVEN[1][j].mx 代表如果最后是 MAXer 先取 已经被 MAXer 改成奇数 的这堆,能取到多少
//EVEN[1][j].mn 代表如果最后是 MAXer 先取 已经被 MINer 改成奇数 的这堆,能取到多少
//因为流程是这样的:
//首先 MINer 和 MAXer 把所有的 EVEN 局面改成 ODD 局面
//改完之后开始处理所有 ODD 局面
int main() {
	int k = in();
	ll mn = 0, mx = 0, res = 0;
	while(k--) {
		n = in();
		_rep(i,1,n) a[i] = in();
		if(n & 1) {
			mn += f(1, n, 0), mx += f(1, n, 1);
		} else { //偶数,分类讨论
			++evcnt;
			_rep(j, 0, 1) EVEN[j][evcnt].init(f(1, n - 1, j), f(2, n, j));
		}
	}
	if(!(evcnt & 1)) { //如果有偶数个“长为偶数的数组”
		res += mx; //最后是MAXer动手取ODD

		int opt = (evcnt & 1) ^ 1; //opt = 1
		sort(EVEN[opt] + 1, EVEN[opt] + 1 + evcnt, greater<Game>());
		_rep(i,1,evcnt) res += (i & 1) ? EVEN[opt][i].mx : EVEN[opt][i].mn;
		//按差值排序后,由于是MAXer先动的手,所以要交替取
	} else {
		res += mn; //最后是MINer先动手取 ODD

		int opt = (evcnt & 1) ^ 1;
		sort(EVEN[opt] + 1, EVEN[opt] + 1 + evcnt, greater<Game>());
		_rep(i,1,evcnt) res += (i & 1) ? EVEN[opt][i].mx : EVEN[opt][i].mn;
	}
	printf("%lld\n", res);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值