【ARC116F】Deque Game 题解 (找规律)

AtC 传送门:ARC116F Deque Game

找规律。

Solution

手玩样例极其重要。

1

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

  • 若数列长度 n 为奇数(m=n+12):高桥先手,答案为 min(Am,max(Am1,Am+1));若青木先手,答案为 max(Am,min(Am1,Am+1))
  • 反之(m=n2):高桥先手,答案为 max(Am,Am+1);青木先手,答案为 min(Am,Am+1)

想要详细严谨的推论?

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

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

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

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

2

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

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

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

    为什么这样能保证最优?对高桥而言,若取它,则会比青木取它让结果总值多出 (mxmn) 个值;对青木而言,若取它,则会比高桥取它让结果总值少掉 (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;
}
posted @   pldzy  阅读(48)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示