zoukankan      html  css  js  c++  java
  • [luogu] P3210 [HNOI2010]取石头游戏(贪心)

    P3210 [HNOI2010]取石头游戏

    题目描述

    A 公司正在举办一个智力双人游戏比赛----取石子游戏,游戏的获胜者将会获得 A 公司提供的丰厚奖金,因此吸引了来自全国各地的许多聪明的选手前来参加比赛。

    与经典的取石子游戏相比,A公司举办的这次比赛的取石子游戏规则复杂了很多:

    l 总共有N堆石子依次排成一行,第i堆石子有 ai个石子。

    l 开始若干堆石子已被 A公司故意拿走。

    l 然后两个玩家轮流来取石子,每次每个玩家可以取走一堆中的所有石子,但有一个限制条件:一个玩家若要取走一堆石子,则与这堆石子相邻的某堆石子已被取走(之前被某个玩家取走或开始被A公司故意拿走)。注意:第 1堆石子只与第 2堆石子相邻,第N堆石子只与第N-1堆石子相邻,其余的第 i堆石子与第i-1堆和第 i+1 堆石子相邻。

    l 所有石子都被取走时,游戏结束。谁最后取得的总石子数最多,谁就获得了这场游戏的胜利。

    作为这次比赛的参赛者之一,绝顶聪明的你,想知道对于任何一场比赛,如果先手者和后手者都使用最优的策略,最后先手者和后手者分别能够取得的总石子数分别是多少。

    输入输出格式

    输入格式:

    第一行是一个正整数N,表示有多少堆石子。输入文件第二行是用空格隔开的N个非负整数a1, a2, ..., aN,其中ai表示第i堆石子有多少个石子,ai = 0表示第i堆石子开始被A公司故意拿走。输入的数据保证0<=ai<=100,000,000,并且至少有一个i使得ai = 0。30%的数据满足2<=N<=100,100%的数据满足2<=N<=1,000,000。

    输出格式:

    仅包含一行,为两个整数,分别表示都使用最优策略时,最后先手者和后手者各自能够取得的总石子数,并且两个整数间用一个空格隔开。

    输入输出样例

    输入样例#1: 复制

    8
    1 2 0 3 7 4 0 9

    输出样例#1: 复制

    17 9

    样例解释:两个玩家都使用最优策略时取走石子的顺序依次为9, 2, 1, 4, 7, 3,因此先手
    者取得9 + 1 + 7 = 17个石子,后手者取得2 + 4 + 3 = 9个石子。

    题解

    去年redbag寒假问我的题目。
    我菜到今年才看懂题目。神仙题。

    对于题目,给定几个双端队列和2个栈。
    问怎么取最合适。
    因为总值是确定的,我们只需要求出差值来。
    最巧妙的地方,
    在于我贪心一定是取当前可以取到的最大的,
    若一个双端队列是单调的或者下凸的,不含上凸就可以了。
    那么我们优先一个一个大的取就可以了。
    为什么?
    可以看一下我的学长举的例子
    https://www.cnblogs.com/Y-E-T-I/p/8555129.html

    如果可取元素都是递减的,比如
    1 2 3 0 2 1 2 0 4 1
    容易发现先手只要贪心地从能取的元素里面拣最大的取走即可。
    这样不会给后手好情况。
    由于每次一定可以取全场最大值,所以只要一次排序然后交替取值即可。
    4 3 2 2 2 1 1 1

    但是样例里就有上凸啊。
    我们可以通过,((a[i]<=a[i+1])&&(a[i+1]>=a[i+2])) 来化为单调或者下凸。
    我们把这三个点看成一个点,取这三个点并不影响后面的顺序。
    一直化减下去,消掉上凸的情况,就只剩下栈的情况了。

    对于栈我们这样取,当且仅当栈内每次取的比小于不能取的时,也就是单增时。我们判断当前石子的奇偶性。
    为什么跟奇偶性相关?可以明确一点,栈内两个为一个周期一取,如果是奇数,那么两端最后取的一定是先手,反之后手。
    举个例子,
    一个栈有7个数,一个栈有4个数,第一个栈依次取进去,最后先手取到,且下一个开头为后手,所以4的也是先手取最后一个。

    若是单调减的栈,直接和双端队列一样排序解决。

    Code

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<cmath>
    #include<iostream>
    #define ll long long
    using namespace std;
    const ll N=1e6+5;
    ll l,r,ans;
    ll n,a[N],sum,tot;
    ll cnt,vis[N],s[N],top;
    ll read(){
    	ll x=0,w=1;char ch=getchar();
    	while(ch>'9'||ch<'0'){if(ch=='-')w=-1;ch=getchar();}
    	while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    	return x*w;
    }
    
    bool cmp(ll a,ll b){return a>b;}
    
    int main(){
    	n=read();
    	for(ll i=1;i<=n;i++){
    		a[++top]=read();sum+=a[top];
    		if(!a[top])vis[top]=1;
    		tot+=vis[top]^1;
    		while(top>=3&&(!vis[top])&&(!vis[top-1])&&(!vis[top-2])&&(a[top]<=a[top-1])&&(a[top-2]<=a[top-1])){
    			a[top-2]=a[top-2]+a[top]-a[top-1];top-=2;
    		}
    	}
    	for(l=1;(!vis[l])&&(!vis[l+1])&&a[l+1]<=a[l];l+=2)
    		ans+=(a[l]-a[l+1])*(tot&1?1:-1);
    	for(r=top;(!vis[r])&&(!vis[r-1])&&a[r]>=a[r-1];r-=2)
    		ans+=(a[r]-a[r-1])*(tot&1?1:-1);
    	for(ll i=l;i<=r;i++)
    		if(!vis[i])s[++cnt]=a[i];
    	sort(s+1,s+cnt+1,cmp);
    	for(ll i=1;i<=cnt;i++){
    		if(i&1)ans+=s[i];
    		else ans-=s[i];
    	}
    	cout<<(sum+ans)/2<<' '<<(sum-ans)/2<<endl;
    	return 0;
    }
    
  • 相关阅读:
    数据流的小结
    二分法小结
    k倍区间
    【图文并茂】如何将英文版的Altium Designer Winter 09改成汉语版?
    【PCB操作】PCB拼板
    【图文并茂】如何将库文件移到另一个库
    PCB的收尾工作之补泪滴
    uCOS-II 学习笔记--------OSInit函数
    uCOS-II 学习笔记之任务管理--------任务就绪表和就绪组
    [leedcode 205] Isomorphic Strings
  • 原文地址:https://www.cnblogs.com/hhh1109/p/10667119.html
Copyright © 2011-2022 走看看