NOIP 2007 提高第三题
题目描述
帅帅经常跟同学玩一个矩阵取数游戏:对于一个给定的n*m的矩阵,矩阵中的每个元素aij均为非负整数。游戏规则如下:
1.每次取数时须从每行各取走一个元素,共n个。m次后取完矩阵所有元素;
2.每次取走的各个元素只能是该元素所在行的行首或行尾;
3.每次取数都有一个得分值,为每行取数的得分之和,每行取数的得分 = 被取走的元素值*2^i,其中i表示第i次取数(从1开始编号);
4.游戏结束总得分为m次取数得分之和。
帅帅想请你帮忙写一个程序,对于任意矩阵,可以求出取数后的最大得分。
输入输出格式
输入格式:
输入文件game.in包括n+1行:
第1行为两个用空格隔开的整数n和m。
第2~n+1行为n*m矩阵,其中每行有m个用单个空格隔开的非负整数。
数据范围:
60%的数据满足:1<=n, m<=30,答案不超过10^16
100%的数据满足:1<=n, m<=80,0<=aij<=1000
输出格式:
输出文件game.out仅包含1行,为一个整数,即输入矩阵取数后的最大得分。
输入输出样例
输入样例#1:
2 3 1 2 3 3 4 2
输出样例#1:
82
思路
TIPS:高精度计算,压位,DP,分治
DP:f[i,j]表示这一行左边取到第i位,右边取到第j位时的最大得分,转移方程:f[i,j]:=max(f[i-1,j]+a[i-1]*(2的n次方),f[i,j+1]+a[j+1]*(2的n次方));
怎么理解转移方程:
举例说明,有矩阵:
a1,a2,a3……an
b1,b2,b3……bn 有N列,但只有2行(这里考虑到书写,所以只列举了2行,大于2行也可以证明)
假设上面矩阵的最大得分取法是:a1*2+b1*2 + a2*22+b2*22……+an*2n+bn*2n,把这个算式变换一下,将an和bn的分开提取,结果变换为a1*2+a2*22……+an*2n + b1*2+b2*22……+bn*2n。我们可以考察一下这个算式会发现:a1*2+a2*22……+an*2n 其实就是第一行a1,a2,a3……an的最大得分, b1*2+b2*22……+bn*2n也是第二行b1,b2,b3……bn 的最大得分。也就是说矩阵的最大得分其实是每一行的最大得分之和,每一行的取数不会和其他行发生联系或冲突的。于是矩阵取数最大得分问题就分解为行取数最大得分问题,只要求出每一行的最大得分,然后求和,便可得出矩阵的最大得分。
再来考察一下单行取数问题的求解。首先,设函数Maxgame(i,j)(i<j),这个函数的功能是:求解一行 ai,a(i+1),a(i+2)……aj 的最大得分。那么a1,a2,a3……an的最大得分用这个函数来表示就是:Maxgame(1,n),那么这个Maxgame(1,n)的值怎样求得呢?我们继续研究。由于取数的时候只能取行首数或是行尾数,于是便有:Maxgame(1,n)=Max((a1*2+Maxgame(2,n)*2),(an*2+Maxgame(1,n-1))),从1到n的行最大得分要么等于取行首元素*2+从2到n的行的最大得分*2;要么等于取行尾元素*2+从1到n-1的行的最大得分。所以,归纳起来Maxgame(1,n)就只有这2种可能性。再看看,此时的问题就被分解为了一个相同的问题,只是数据规模小了1:去掉了a1或an,剩下的a2,a3,……an或a1,a2,……a(n-1)成为新的待求最大得分的行。如此进行下取,当这一行只剩下2个数时:ax,ay(x必然等于y-1,ax和ay是相邻的2个数),则只需要先取min(ax,ay),最后一步取max(ax,ay)。该问题迎刃而解。
解释来源:http://mynoi.blog.163.com/blog/static/8356718420085351216351/
const ma=10000;//压到万进制 type arr=array[0..80] of longint;//这是一个高精度数 var f:array[-1..82,-1..82] of arr; a:array[0..81,0..81] of longint; s2:array[0..80] of arr; i,j,k,n,m:longint; ss,sum,a1,a2,a3,a4:arr; function max(a,b:arr):arr; var z:longint; begin if a[0]>b[0] then exit(a); if b[0]>a[0] then exit(b); for z:=a[0] downto 1 do begin if a[z]>b[z] then exit(a); if b[z]>a[z] then exit(b); end; exit(a); end; //高精度比较大小 function plus(a,b:arr):arr; var z,l:longint; begin if a[0]>b[0] then l:=a[0] else l:=b[0]; if l=0 then l:=1; for z:=1 to l do begin a[z+1]:=a[z+1]+(a[z]+b[z]) div ma; a[z]:=(a[z]+b[z]) mod ma; end; plus:=a; if a[l+1]<>0 then plus[0]:=l+1 else plus[0]:=l;//处理最高位 end; //高精度加法 function multiply(b:longint;a:arr):arr; var l,z:longint; begin l:=a[0]; if l=0 then l:=1; for z:=1 to l do a[z]:=a[z]*b; for z:=1 to l do begin a[z+1]:=a[z+1]+a[z] div ma; a[z]:=a[z] mod ma; end; while a[l+1]<>0 do begin inc(l); a[l+1]:=a[l] div ma; a[l]:=a[l] mod ma; end; //处理最高位 multiply:=a; multiply[0]:=l; end; //高精度乘法 procedure init; var i,j:longint; begin readln(n,m); for i:=1 to n do begin for j:=1 to m do read(a[i,j]); readln; end; end; procedure change; begin fillchar(a,sizeof(a),0); fillchar(f,sizeof(f),0); fillchar(sum,sizeof(sum),0); end; procedure work; var i,j:longint; begin s2[1][1]:=2; s2[1][0]:=1; for i:=2 to m do s2[i]:=multiply(2,s2[i-1]); end; procedure main; var i,j,k:longint; begin for k:=1 to n do begin for i:=0 to m do for j:=m+1 downto i+1 do begin a1:=multiply(a[k,i],s2[i+m-j+1]); a2:=multiply(a[k,j],s2[i+m-j+1]); a3:=plus(f[i-1,j],a1); a4:=plus(f[i,j+1],a2); f[i,j]:=max(a3,a4); end; ss:=f[0,1]; for i:=1 to m-1 do ss:=max(ss,f[i,i+1]); sum:=plus(sum,ss); end; end; procedure printf; var i:longint; begin write(sum[sum[0]]); for i:=sum[0]-1 downto 1 do begin if sum[i]<10 then write('000'); if (sum[i]>=10)and(sum[i]<100) then write('00'); if (sum[i]>=100)and(sum[i]<1000) then write('0'); write(sum[i]); end; writeln; end; begin change; init; work; main; printf; end.