bzoj2017[Usaco2009 Nov]硬币游戏*
题意:
初始时,一个有N枚硬币的堆栈放在地上,每枚硬币都有一个价值。开始玩游戏时,第一个玩家可以从堆顶拿走一枚或两枚硬币。之后每一轮中,当前的玩家至少拿走一枚硬币,至多拿走对手上一次所拿硬币数量的两倍。当没有硬币可拿时,游戏结束。 两个玩家都希望拿到最多钱数的硬币。求第一个玩家最多能拿多少钱。n≤2000。
题解:
首先有dp方程:f[i][j][0]=max(f[i][k][1]+sum(i-k,i)),1≤k≤min(j*2,i),f[i][j][1]=min(f[i][k][0]),1≤k≤min(j*2,i)。
然而这样会超时。发现所以f[i][k][1]的最大值实际上是f[i][j*2][1]、f[i][j*2-1][1]、f[i][j-1][0]的最大值,f[i][k][0]的最小值也是f[i][j*2][0]、f[i][j*2-1][0]、f[i][j-1][1]的最小值。故可以把dp优化到O(n^2)可过。
代码:
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 #define inc(i,j,k) for(int i=j;i<=k;i++) 5 #define maxn 2010 6 using namespace std; 7 8 inline int read(){ 9 char ch=getchar(); int f=1,x=0; 10 while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();} 11 while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar(); 12 return f*x; 13 } 14 int a[maxn],sm[maxn],f[maxn][maxn][2],n; 15 int main(){ 16 n=read(); inc(i,1,n)a[i]=read(); for(int i=n;i>=1;i--)sm[n-i+1]=sm[n-i]+a[i]; 17 inc(i,1,n) 18 inc(j,1,n){ 19 f[i][j][0]=j>1?f[i][j-1][0]:0; int k; 20 k=2*j-1; if(k<=i)f[i][j][0]=max(f[i][j][0],f[i-k][k][1]+sm[i]-sm[i-k]); 21 k=2*j; if(k<=i)f[i][j][0]=max(f[i][j][0],f[i-k][k][1]+sm[i]-sm[i-k]); 22 f[i][j][1]=j>1?f[i][j-1][1]:0x3fffffff; 23 k=2*j-1; if(k<=i)f[i][j][1]=min(f[i][j][1],f[i-k][k][0]); 24 k=2*j; if(k<=i)f[i][j][1]=min(f[i][j][1],f[i-k][k][0]); 25 } 26 printf("%d",f[n][1][0]); return 0; 27 }
20160830