1. 程式人生 > 實用技巧 >Luogu3210 [HNOI2010]取石頭遊戲

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!\)

\(C++ 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;
}