• 【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 
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
  • 相关阅读:
    Web后端的前端:揭秘跨界融合的深度探索
    最新版MySQL 8.0 的下载与安装(详细教程)
    开发从0 到1获取代码,提交,推送
    什么是“空洞卷积”
    腾讯云服务器CVM和轻量应用服务器区别全方位对比
    竞赛选题 深度学习 大数据 股票预测系统 - python lstm
    奇葩问题:arp缓存、ip地址冲突(实际是ip地址被占用导致arp缓存出现问题)
    Python数据分析与机器学习20- 逻辑回归项目实战4-模型评估方法:混淆矩阵
    Python语言学习实战-内置函数reduce()的使用(附源码和实现效果)
    应该记住的10个SQL 查询
  • 原文地址:https://blog.csdn.net/ez_gsn/article/details/125887215