[NOIP2007]矩陣取數游戲


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.
View Code
 
        

 

 
 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM