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.