【动态规划】数字游戏
【动态规划】数字游戏
【问题描述】丁丁最近沉迷于一个数字游戏之中。这个游戏看似简单,但丁丁在研究了许多天之后却发觉原来在简单的规则下想要赢得这个游戏并不那么容易。游戏是这样的,在你面前有一圈整数(一共n个),你要按顺序将其分为m个部分,各部分内的数字相加,相加所得的m个结果对10取模后再相乘,最终得到一个数k。游戏的要求是使你所得的k最大或者最小。例如,对于下面这圈数字(n=4,m=2):
当要求最小值时,((2-1) mod 10)×((4+3) mod 10)=1×7=7,要求最大值时,为((2+4+3) mod 10)×(-1 mod 10)=9×9=81。特别值得注意的是,无论是负数还是正数,对10取模的结果均为非负值。丁丁请你编写程序帮他赢得这个游戏。
【输入格式】输入文件第一行有两个整数,n(1≤n≤50)和m(1≤m≤9)。以下n行每行有个整数,其绝对值不大于104,按顺序给出圈中的数字,首尾相接。
【输出格式】输出文件有两行,各包含一个非负整数。第一行是你程序得到的最小值,第二行是最大值。
【输入样例】
4 2
4
3
-1
2
简要分析
很明显这是一道动态规划题(满足动态规划的两个特征:最优解结构, 重叠子问题),既然是环形结构,我们就可以通过枚举断点(这一点有些不明白)把它变成链式结构。设F[I,j,k] 表示从第i个数到第j个数分成k个部分的最优值,A[i]表示前i个数之和。则有:
F[I,j,l] = Max or Min { F[I,k,l-1] + Sum } ( 1 <= l <= m – 1 )(这个就是所谓的状态转移方程式)
Sum的求法如下:
If k < j then Sum = A[j] – A[k]
Else Sum = A[n] – A[k] + A[j]
Sum = (10 + Sum mod 10 ) mod 10
这个过程的时间复杂度O(n^3 * m)
答案即为 Max or Min { F[I,j,m-1] + Sum }
Sum的求法如前面所述,这个过程的时间复杂度为O(n^2)
2
4
-1
3
【输出样例】
7
81
A数组可以在O(n)的预处理时间里求得。
所以此题总的时间复杂度为O(n^3 * m)
空间复杂度为O(n^2)
注意事项
负数队10取余时没必要一个10一个10的加,求Sum时也不用开一个二维数组来保存。
Mysol
var
i,j,k,n,m,maxt,mint,tmp,p,l:longint;
f,g:array[-5..101,-5..101,-5..101] of longint;
a,sum:array[-5..101] of longint;
function min(a,b:longint):longint;
begin
if a<b then exit(a)
else exit(b);
end;
function max(a,b:Longint):longint;
begin
if a>b then exit(a)
else exit(b);
end;
procedure init;
begin
readln(n,m);
filldword(f,sizeof(f)>>2,$00000001);
filldword(g,sizeof(g)>>2,$00000001);//全设为一,因为要乘
for i:=1 to n do
begin
readln(a[i]);
sum[i]:=sum[i-1]+a[i];
end;
for i:=n+1 to 2*n do
begin
a[i]:=a[i-n];
sum[i]:=sum[i-1]+a[i];
end;
end;
procedure main;
begin
for i:=1 to 2*n do
for j:=1 to 2*n do
if (j-i+1<=n) and (j>i) then
begin;
f[i,j,0]:=(10+(sum[j]-sum[i-1]) mod 10) mod 10;
g[i,j,0]:=f[i,j,0];
end;//一个都不分时,就是本身的值(初始化很重要,做这种区间动归题,一般是把小区间先赋出来,再找大区间);
for k:=1 to m-1 do
for l:=2 to n do
for i:=1 to 2*n do
begin
j:=i+l-1;
if (j<=2*n) then
begin
f[i,j,k]:=0;
g[i,j,k]:=1000000;
for p:=i to j-1 do
begin
if (p=i) and (k<>1) then continue;//比如说取f[1,1,2] 是无意义的,退出(注意一些无意义的值);
tmp:=(10+(sum[j]-sum[p]) mod 10) mod 10;
f[i,j,k]:=max(f[i,j,k],f[i,p,k-1]*tmp);
g[i,j,k]:=min(g[i,j,k],g[i,p,k-1]*tmp);
end;
end;
end;
maxt:=0;
mint:=1000000;
for i:=1 to n do
if f[i,i+n-1,m-1]>maxt then maxt:=f[i,i+n-1,m-1];
for i:=1 to n do
if g[i,i+n-1,m-1]<mint then mint:=g[i,i+n-1,m-1];
writeln(mint);
writeln(maxt);
end;
begin
assign(input,'game.in');reset(input);
assign(output,'game.out');rewrite(output);
init;
main;
close(input);
close(output);
end.
反思:
这道题自己改了一天,真是烦透了,但是当最后时刻改对时,真的太高兴了,也许这就是信息奥赛的魅力吧;
改了一天,终于在最后一小时学会怎么改错了,原来自己以前从来不会改错,只是盲目的去蒙;
我总结的改错的基本步骤:
1:先查一遍语法错误及逻辑错误;
2:测样例数据;
3:自己编一些数据(按曹老师说的那样);
4:若过不了,别慌,认真思考(这很重要,以前自己从来不会思考,浪费了很多时间);
5:再认真审几遍题,看是否理解错了题意;
6:看初始化是否正确;
7:自己手工简单模拟一遍小数据;
8:动态差错,认真记录,思考;
9:有时找到了错误根源,但不太好改,换多种思维;
10:实在不行,不如大胆一些换程序,重要的是相信自己;
静下心来,认真体悟信息学奥赛的精髓吧!
I‘m on the way!

浙公网安备 33010602011771号