UVA-10891 Game of Sum

题目大意:

俩小孩玩游戏, 轮流从左边或者右边选择连续若干个数( 除非全选, 否则只能选一边 ), 它们的和就是这个小孩这一次的得分, 然后删去选择的数, 问先手的小孩最多可以比后手的多多少分. 

很明显的dp题是不是?

那么怎么设状态呢? dp[ i ][ j ]表示在[ l , r ]这一段区间内, 先手可以得到的最大分数. 那么怎么转移呢? 想来想去也没有什么想法.

这时候注意到一个地方: 每次只能选左边或者右边的若干个数, 那么假设现在剩下的是[ l , r ]这一段的数, 先手选择了[ l , k ], 剩下的就是[ k+1 , r ]; 先手选择[ k , r ], 剩下的就是[ l , k-1 ].

而剩下的又是一个类似的子问题了. 所以必定是从dp[ l ][ k ]和dp[ k ][ r ]转移( 其中k∈[ l , r ] ).

考虑到状态是先手的最大分数, 而这一轮先手选择之后, 后手变先手, 那么原后手也是按照最优策略选择, 所以也是枚举了子区间转移.

由于存在先后手转换, 而且二者都是按照最优策略, 所以当前先手肯定枚举k, 在两种方向( 选右边或者选左边 )中选择子区间最优策略下自己剩的分最多的方案.

而给自己能够剩的分是sum[ l ][ k ]-dp[ l ][ k ]或者sum[ k ][ j ]-dp[ k ][ j ].

这样转移就很明显了, 记得当前选择之后也有选择的分, 也要加入转移取max:

dp[ i ][ j ]=max( sum[ l ][ k ]-dp[ l ][ k ]+sum[ k+1 ][ r ] , sum[ k ][ r ]-dp[ k ][ r ]+sum[ l ][ k-1 ] ).

化简一下就是:

dp[ i ][ j ]=max( -dp[ l ][ k ] , -dp[ k ][ r ] )+sum[ l ][ r ].

sum用前缀和搞一下, 枚举一下k, 注意初状态为0和多组数据, 剩下的就是没有什么技术含量的码代码了.

//made by Crazy01
#include<queue>
#include<math.h>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
#define inf 1<<30
#define ll long long
#define db double
#define c233 cout<<"233"<<endl
#define mem(s) memset(s,0,sizeof(s))
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
const int N=105;
using namespace std;

int dp[N][N],sum[N],a[N];
int n;

inline int gi(){
  int x=0,res=1;char ch=getchar();
  while(ch>'9'||ch<'0'){if(ch=='-')res*=-1;ch=getchar();}
  while(ch<='9'&&ch>='0')x=(x<<1)+(x<<3)+ch-48,ch=getchar();
  return x*res;
}

void init(){
  n=gi(); if(!n)exit(0);
  for(int i=1;i<=n;i++)
    a[i]=gi(),sum[i]=sum[i-1]+a[i];
}

void work(){
  for(int det=1;det<=n;det++)
    for(int i=1;i+det-1<=n;i++){
      if(det==1){dp[i][i]=a[i]; continue;}
      int j=i+det-1; dp[i][j]=-inf;
      for(int k=i;k<j;k++)
	dp[i][j]=max(dp[i][j],-dp[i][k]);
      for(int k=j;k>i;k--)
	dp[i][j]=max(dp[i][j],-dp[k][j]);
      dp[i][j]=max(dp[i][j],0);
      dp[i][j]+=sum[j]-sum[i-1];
    }
  printf("%d\n",dp[1][n]-sum[n]+dp[1][n]);
}

int main(){
  while(1){
    init();
    work();
  }
  return 0;
}  

顺带说一句, 这一题是我人生压行的巅峰, 上面那个代码被我压成了这个鬼样子( 除开made by Crazy01只有323B, 只是为了争夺vjudge上这一题的rank 1....):

//made by Crazy01
#include<iostream>
#define P f[i][j]
using namespace std;int f[105][105],s[105],n,i,w,k,j;int main(){cin>>n;while(n){for(i=1;i<=n;i++)cin>>s[i],s[i]+=s[i-1];for(w=0;w<n;w++)for(i=1;i+w<=n;i++){j=i+w;P=0;for(k=i;k<j;k++)P=max(P,-f[i][k]);for(k=j;k>i;k--)P=max(P,-f[k][j]);P+=s[j]-s[i-1];}cout<<f[1][n]*2-s[n]<<endl;cin>>n;}}

转载于:https://www.cnblogs.com/Crazy01/p/7665880.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值