您的位置:首页 > 其它

UVa10891 Game of Sum(dp)

2017-10-08 09:34 375 查看
简述:

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]
-(sum(1,n)-f[1]
)

时间复杂度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
;
int f

;

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]
-(sum
-f[1]
));
}

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]
-(sum
-f[1]
));
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: