Luogu3210 [HNOI2010]取石头游戏

https://www.luogu.com.cn/problem/P3210

贪心

我们维护先手得分与后手得分的差值

结论:

在任何情况下,如果能够取到整个序列的最大值,那么取到整个序列的最大值一定最优

证明:

假设有一个对手,与你同时下同一种局面下的两个游戏

在第一局中,对手执先手,在第二种局面中,你执先手

你的策略是上述结论,你只需要保证第二种局面不会比第一种更劣就好了

\(Round 1\)

第一局:

对手\(p_1\)\(x\)

\(q_1\)\(Max\)

第二局:

\(q_2\)\(Max\)

对手\(p_2\)\(y\)

接下来的几轮,你就模仿对手就好了,对手取啥,你就取啥

有时你取不到对手取的东西,你就取对手取的位置旁边的\(0\)的同一方向的第一个位置就好了

这样保证满足:\(q_2=p_1-\{x^{'}\}+\{M\},q_1=p_2-\{y^{'}\}+\{M\}\)(注:\(x^{'},y^{'}\)不一定是\(x,y\))

观察上面的式子,你惊奇地发现你总是更优

一旦在一轮两局中出现相同的局面,就可以直接结束(你可以完全模仿对方啦!)

证明完毕!

但是\(Max\)不一定能够取到,我们先考虑一下简化序列

对于序列\(x,y,z(x<y,z<y)\),先后手肯定都想取\(z\),所以没人会先取,除非不得已

如果一定要取,一定是不得已了,那么一定会一口气取完,先取的产生贡献\(x+z-y\)

这样我们可以把所有序列(中间用\(0\)隔开)简化成单调递减、单调递增或凹形的序列

对于不在两端的序列,我们随时可以取得它的最大值,一个个取就是最优解

对于首尾的序列,对于首单调递增的那部分,尾单调递减的那部分,我们可以按照上面的方法处理

麻烦的是剩余的那部分(这里包括山谷),考虑一下,会不会在不在两端的序列还没取完的时候,就有人去取它了呢?

假设先手取了,那么说明在如果最后自动分配时,这个数是后手取的(对先手不利)

那么显然,后手不会继续取(隔一个取的)

根据奇偶性,后手依旧可以取到相同奇偶性的较优秀的方案

所以,我们只需要把山谷也当成可以直接取的来处理就好了

\(OK!\)

\(Code:\)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
#define N 1000005
#define _INF -123456789987654321
#define ll long long
using namespace std;
int cnt,_n,n,L,R,RL,RR;
ll _s,s=0,st,ans=0,x,a[N];
priority_queue<ll>q;
int main()
{
    scanf("%d",&_n);
    L=_n+1,R=0;
    for (int i=1;i<=_n;i++)
    {
        scanf("%lld",&x);
        s+=x;
        if (!x)
        {
            a[++n]=_INF;
            continue;
        }
        a[++n]=x;
        while (n>2 && a[n-1]!=_INF && a[n-2]!=_INF && a[n-2]<a[n-1] && a[n-1]>a[n])
        {
            a[n-2]=a[n-2]+a[n]-a[n-1];
            n-=2;
        }
    }
    for (int i=1;i<=n;i++)
        if (a[i]!=_INF)
            cnt++;
    for (L=1;a[L]!=_INF && a[L+1]!=_INF && a[L]>=a[L+1];L+=2)
    {
        if (cnt & 1)
            ans+=a[L]-a[L+1]; else
            ans+=a[L+1]-a[L];
    }
    for (R=n;a[R]!=_INF && a[R-1]!=_INF && a[R]>=a[R-1];R-=2)
    {
        if (cnt & 1)
            ans+=a[R]-a[R-1]; else
            ans+=a[R-1]-a[R];  
    }
    for (;L<=R;L++)
        if (a[L]!=_INF)
            q.push(a[L]);
    st=1;
    while (!q.empty())
    {
        ans+=st*q.top();
        q.pop();
        st=-st;
    }
    printf("%lld %lld\n",(s+ans) >> 1,(s-ans) >> 1);
    return 0;
}
posted @ 2020-08-03 21:03  GK0328  阅读(103)  评论(0编辑  收藏  举报