取石子
题目链接:NOI2022省选挑战赛 Contest4 B
题目大意
给你一个序列,两个人轮流选头部或尾部拿走那个数,然后双方都要使得自己拿到的数的和尽可能大。
然后问你先手能有的最大和。
思路
首先看到第三个部分分,是一个山谷的形式。
那我们肯定是每次都选两半之间最大的,亦或者是说每次选当前剩下中最大的。
那就是排个序,然后奇数位置的给先手,偶数位置的给后手。
然后考虑如果有小山峰(
x
i
⩽
x
i
+
1
⩾
x
i
+
2
x_i\leqslant x_{i+1}\geqslant x_{i+2}
xi⩽xi+1⩾xi+2),那我们考虑会怎样。
如果选到了这个两边,那中间这个
x
i
+
1
x_{i+1}
xi+1 是肯定会选的,而它因为
x
i
+
2
x_{i+2}
xi+2 是小的,所以选中间的那个人一定会把它留给选
x
i
x_i
xi 的(从另外一边也同理)
那其实我们可以把这三个压成一个数(
x
i
+
x
i
+
2
−
x
i
+
1
x_i+x_{i+2}-x_{i+1}
xi+xi+2−xi+1)。
然后我们就可以每次对山峰这么做一次,做到只剩山谷,然后再搞。
那显然这样我们直接搞先手选的不太方便,考虑先求出先手比后手多的(
a
n
s
ans
ans),然后你设两个的量是
x
,
y
x,y
x,y,全部的量是
s
u
m
sum
sum。
那
x
+
y
=
s
u
m
,
x
−
y
=
a
n
s
x+y=sum,x-y=ans
x+y=sum,x−y=ans,然后就
x
=
s
u
m
+
a
n
s
2
x=\dfrac{sum+ans}{2}
x=2sum+ans。
代码
#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;
}