UVa10891 Game of Sum(dp)

来源:互联网 发布:python开发过哪些软件? 编辑:程序博客网 时间:2024/05/24 03:41

简述:
n的序列,两人轮流从两端取数,
两人都选择最优策略,求两人的得分之差

分析:
一开始觉得是一道博弈
但是可以用dp解决的
这就和Tyvj上的硬币游戏有异曲同工之妙

因为只能取一个连续的区间
所以剩下的一定是原序列中的一个连续区间
这就指引我们往区间dp的方向上想
设计状态:f[i][j]表示现在剩下i~j的序列,先手的最高得分
f[i][j]=sum(i,j)-min{f[i+1][j],f[i+2][j],…,f[j][j],f[i][j-1],f[i][j-2],…,f[i][i],0}
注意,0表示先手全部取走
最后答案是
f[1][n]-(sum(1,n)-f[1][n])

时间复杂度O(n^3)

tip

当面前只有一个数的时候,必须取

//这里写代码片#include<cstdio>#include<cstring>#include<iostream>using namespace std;const int INF=1e9;const int N=110;int n;int sum[N];int f[N][N];void dp(){    int i,j,k;    memset(f,0,sizeof(f));    for (int i=1;i<=n;i++) f[i][i]=sum[i]-sum[i-1];   //必须取数     for (int i=n;i>=1;i--)        for (int j=i+1;j<=n;j++)        {            int minn=0;            for (k=i;k<j;k++)            {                minn=min(minn,f[i][k]);                minn=min(minn,f[k+1][j]);            }            f[i][j]=sum[j]-sum[i-1]-minn;        }    printf("%d\n",f[1][n]-(sum[n]-f[1][n]));}int main(){    scanf("%d",&n);    while (n)    {        sum[0]=0;        for (int i=1;i<=n;i++) scanf("%d",&sum[i]),sum[i]+=sum[i-1];        dp();        scanf("%d",&n);    }    return 0;}

然而

我们还可以让时间复杂去更优一点
我们发现转移的时候,min值的计算是很有规律的
所以我们记
g1[i][j]=min{f[i][j],f[i+1][j],f[i+2][j],…,f[j][j]}
g2[i][j]=min{f[i][j],f[i][j-1],f[i][j-2],…,f[i][i]}

转移就变成了:
f[i][j]=sum(i,j)-min{g1[i+1][j],g2[i][j-1],0}
不要忘了0

g1和g2的转移也很好维护
g1[i][j]=min(g1[i+1][j],f[i][j])
g2[i][j]=min(g2[i][j-1],f[i][j])

这样优化之后,时间复杂度就降到了n^2

void dp(){    int i,j,k;    for (int i=1;i<=n;i++)     {        f[i][i]=sum[i]-sum[i-1];        g1[i][i]=f[i][i];        g2[i][i]=f[i][i];    }    for (i=n;i>=1;i--)        for (j=i+1;j<=n;j++)        {            int minn=0;            minn=min(minn,g1[i+1][j]);            minn=min(minn,g2[i][j-1]);            f[i][j]=sum[j]-sum[i-1]-minn;            g1[i][j]=min(g1[i+1][j],f[i][j]);            g2[i][j]=min(g2[i][j-1],f[i][j]);        }    printf("%d\n",f[1][n]-(sum[n]-f[1][n]));}
原创粉丝点击