1. 程式人生 > 其它 >【NOI2022省選挑戰賽 Contest4 B】取石子(博弈論)

【NOI2022省選挑戰賽 Contest4 B】取石子(博弈論)

給你一個序列,兩個人輪流選頭部或尾部拿走那個數,然後雙方都要使得自己拿到的數的和儘可能大。 然後問你先手能有的最大和。

取石子

題目連結:NOI2022省選挑戰賽 Contest4 B

題目大意

給你一個序列,兩個人輪流選頭部或尾部拿走那個數,然後雙方都要使得自己拿到的數的和儘可能大。
然後問你先手能有的最大和。

思路

首先看到第三個部分分,是一個山谷的形式。

那我們肯定是每次都選兩半之間最大的,亦或者是說每次選當前剩下中最大的。
那就是排個序,然後奇數位置的給先手,偶數位置的給後手。

然後考慮如果有小山峰(\(x_i\leqslant x_{i+1}\geqslant x_{i+2}\)),那我們考慮會怎樣。
如果選到了這個兩邊,那中間這個 \(x_{i+1}\) 是肯定會選的,而它因為 \(x_{i+2}\)

是小的,所以選中間的那個人一定會把它留給選 \(x_i\) 的(從另外一邊也同理)

那其實我們可以把這三個壓成一個數(\(x_i+x_{i+2}-x_{i+1}\))。
然後我們就可以每次對山峰這麼做一次,做到只剩山谷,然後再搞。

那顯然這樣我們直接搞先手選的不太方便,考慮先求出先手比後手多的(\(ans\)),然後你設兩個的量是 \(x,y\),全部的量是 \(sum\)
\(x+y=sum,x-y=ans\),然後就 \(x=\dfrac{sum+ans}{2}\)

程式碼

#include<cstdio>
#include<algorithm>
#define ll long long

using namespace std;

int n, x, m;
ll a[1000001], ans, sum;

bool cmp(ll x, ll y) {
	return x > y;
}

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) {
		scanf("%d", &x); sum += x;
		a[++m] = x;
		while (m > 2 && a[m - 2] <= a[m - 1] && a[m - 1] >= a[m]) {
			a[m - 2] = a[m - 2] + a[m] - a[m - 1];
			m -= 2;
		}
	}
	sort(a + 1, a + m + 1, cmp);
	
	for (int i = 1; i <= m; i++)
		ans += (i & 1) ? a[i] : -a[i];
	printf("%lld", (ans + sum) / 2);
	
	return 0;
}