[关键字]:排序 贪心
[题目大意]:给出n堆石头,并在初始时取走某几堆,每次只能去相邻石头堆是空的石头堆,计算双方都是最优的情况下的石头数。
//===========================================================================================================
[分析]:用空堆将石头分开,可以分成两个栈和若干个双头队列。如果只有一个栈,则每个选手所能取到的数是唯一的,如果只有一个队列如果是奇数个先手可以取奇数位的和和偶数位的和的最大值。如果是组合的话就复杂了。如果在栈里出现栈顶小于下一个则除非不得已没人回取这个而根据总个数的限制我们可以算出最后谁取了那一个然后删掉他们。而如果在队列里出现了 a b c 且b>a b>c则可以化简成一个a+c-b因为如果有人取a或c则另一个人只有取b才能防止对手更优,此时取a或c的人只有取另一个才能更优,如果此时有比取另一个更优的值那一开是就不取才是更优解,所以只要a、b、b一个被取走,另外的一定也会被取走。这样化简完后两个栈里的元素一定是递减而队列里的元素用线表示的话一定是平的或凹的。所以此时贪心成立,每次都取可以取的元素中最大的就行了,只要排序然后按奇偶性取就行了,因为化简时吧元素变成了先后手只差的形式,所以要记录所有的和然后列方程解出来。
[代码]:
View Code
#include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> #include<algorithm> using namespace std; const int MAXN=1010000; int sum,n,dif,cnt,tot; int a[MAXN],c[MAXN],l[MAXN],r[MAXN]; bool b[MAXN]; bool cmp(int x,int y) {return x>y;} void Work(int pos) { b[pos]=1; if (l[pos]>=1) l[r[pos]]=l[pos]; if (r[pos]<=n) r[l[pos]]=r[pos]; } int main() { freopen("in.txt","r",stdin); freopen("out.txt","w",stdout); scanf("%d",&n); for (int i=1;i<=n;++i) { scanf("%d",&a[i]); sum+=a[i]; if (a[i]==0) b[i]=1; else ++cnt; l[i]=i-1,r[i]=i+1; } int h=1,t=n; bool flag=1; while (flag && h<t) { flag=0; while (!b[h] && !b[r[h]] && a[h]>=a[r[h]]) { if (cnt&1) dif+=a[h]-a[r[h]]; else dif+=a[r[h]]-a[h]; flag=1; int temp=r[r[h]]; Work(r[h]),Work(h); h=temp; } while (!b[t] && !b[l[t]] && a[t]>=a[l[t]]) { if (cnt&1) dif+=a[t]-a[l[t]]; else dif+=a[l[t]]-a[t]; flag=1; int temp=l[l[t]]; Work(t),Work(l[t]); t=temp; } for (int i=h;i<=t;++i) if (!b[i] && l[i]>=1 && r[i]<=n) if (!b[l[i]] && !b[r[i]]) if (a[i]>=a[l[i]] && a[i]>=a[r[i]]) { flag=1; a[i]=a[r[i]]+a[l[i]]-a[i]; if (h==l[i]) h=i; if (t==r[i]) t=i; Work(l[i]),Work(r[i]); } } for (int i=1;i<=n;++i) if (!b[i]) c[++tot]=a[i]; sort(c+1,c+tot+1,cmp); for (int i=1;i<=n;++i) if (i&1) dif+=c[i]; else dif-=c[i]; printf("%d %d\n",(sum+dif)/2,(sum-dif)/2); fclose(stdin); fclose(stdout); return 0; }