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