1. 程式人生 > >【BZOJ2000】[HNOI2000]取石頭遊戲(貪心,博弈論)

【BZOJ2000】[HNOI2000]取石頭遊戲(貪心,博弈論)

lse long long == 並且 分開 top ans 題解 ++i

【BZOJ2000】[HNOI2000]取石頭遊戲(貪心,博弈論)

題面

BZOJ
洛谷

題解

這題好神仙啊,窩不會QaQ。
假裝一下只有三個元素\(a_{i-1},a_i,a_{i+1}\),並且滿足,\(a_{i-1}\le a_i\ge a_{i+1}\)那麽肯定是\(a_{i-1}+a_{i+1}\)\(a_i\)這樣子分配的。那麽兩個人的差就是\(a_{i-1}+a_{i+1}-a_i\),那麽我們把\(i\)和旁邊兩個元素直接合並就好了,反正只要知道了兩個人的差和所有元素之和就能還原答案。
不難發現這樣子合並完之後序列要麽單增要麽單減。
我們發現中間被分開的一段段是一個雙端隊列,可以從兩端取。兩側被分割的部分是一個棧,只能一側取。顯然兩側的按照奇偶可以直接分配好誰去哪一側。而剩下的部分因為單調,所以顯然排序之後兩個人一個個輪流取就好了。

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define ll long long
#define MAX 1000100
inline int read()
{
    int x=0;bool t=false;char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=true,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return t?-x:x;
}
int n,l,r,fr,top;ll S[MAX],sum,ans;bool vis[MAX];
bool check(int p){if(vis[p]||vis[p-1]||vis[p+1])return false;return S[p-1]<=S[p]&&S[p]>=S[p+1];}
int main()
{
    n=read();
    for(int i=1;i<=n;++i)
    {
        S[++top]=read();sum+=S[top];vis[top]=(S[top]==0);fr^=(bool)(S[top]);
        while(top>=3&&check(top-1))S[top-2]=S[top-2]+S[top]-S[top-1],top-=2;
    }
    for(l=1;!vis[l]&&!vis[l+1]&&S[l]>=S[l+1];l+=2)ans+=(S[l]-S[l+1])*(fr?1:-1);
    for(r=top;!vis[r]&&!vis[r-1]&&S[r]>=S[r-1];r-=2)ans+=(S[r]-S[r-1])*(fr?1:-1);
    top=0;for(int i=l;i<=r;++i)if(!vis[i])S[++top]=S[i];sort(&S[1],&S[top+1]);
    for(int i=top;i;--i)ans+=((top-i)&1)?-S[i]:S[i];
    cout<<(sum+ans)/2<<' '<<(sum-ans)/2<<endl;
    return 0;
}

【BZOJ2000】[HNOI2000]取石頭遊戲(貪心,博弈論)