1. 程式人生 > >洛谷 P1005 矩陣取數遊戲

洛谷 P1005 矩陣取數遊戲

每次 狀態轉移方程 latex 輸出格式 scan 有一個 練手題 開始 define

題目描述

帥帥經常跟同學玩一個矩陣取數遊戲:對於一個給定的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

說明

NOIP 2007 提高第三題

吐槽

  一道DP練手題,2007年時這題是靠高精才撐到那麽高的難度的,C++11裏的__int128對付這題簡直變態,數據範圍到$2^{90}$,long long不夠,__int128剛好……

  常年被大神鄙視,RP積攢了好多,寫某些程序都自帶小常數。這題我11ms,在洛谷恐怕是rank1了吧,即使不是也是前十(大牛分站出了點問題,暫時看不了排名)。翻了6頁記錄,200ms以內的全用__int128,其他版本的高精度耗時從230ms到2000ms不等。

解題思路

  不算高精度,就是一道簡單的DP,我們發現每一行都可以獨立計算,最後統計答案即可。對於每一行,我們用$f[i][j]$(LaTeX上癮了)表示這行還剩下$[i,j]$時能得到的最高分,那麽狀態轉移方程就顯然了——$f[i][j]=max( f[i-1][j]+2^{m-j+i}*a[i-1] , f[i][j+1]+2^{m-j+i}*a[j+1] )$//上一步是從左取還是從右取呢?

  邊界是j>=i,這時f[i][i]表示的只是a[i]兩邊都被取時的最大得分,要得到這一行取完的得分,還要加上$a[i]*2^{m}$。

  最後,__int128輸出實在是坑,要寫“快寫”,還要特判0,第一個點答案是0,第一次沒特判90分。

源代碼

#include<bits/stdc++.h>
#define lll __int128
void print(lll x)
{
    if (x==0) return;
    if (x) print(x/10);
    putchar(x%10+0);
}

int n,m;
lll ans=0;
int a[100]={0};
lll f[100][100];
lll p[100]={1};

lll dp()
{
    memset(f,0,sizeof(f));
    for(int i=1;i<=m;i++)
    {
        for(int j=m;j>=i;j--)
        {
            f[i][j]=std::max( f[i-1][j]+ p[m-j+i-1]*a[i-1]  , f[i][j+1]+ p[m-j+i-1]*a[j+1] );
        }
    }
    lll maxn=-1;
    for(int i=1;i<=m;i++) maxn=std::max(maxn,f[i][i]+a[i]*p[m]);
    return maxn;
}

int main()
{
    for(int i=1;i<=90;i++) p[i]=p[i-1]<<1;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
            scanf("%d",a+j);
        ans+=dp();
    }
    if(ans==0) puts("0");
    else print(ans);
    return 0;
}

洛谷 P1005 矩陣取數遊戲