題目描述
帥帥經常跟同學玩一個矩陣取數游戲:對於一個給定的${n}\times{m}$的矩陣,矩陣中的每個元素${a}{_i}{_j}$均為非負整數。游戲規則如下:
1.每次取數時須從每行各取走一個元素,共n個。m次后取完矩陣所有元素;
2.每次取走的各個元素只能是該元素所在行的行首或行尾;
3.每次取數都有一個得分值,為每行取數的得分之和,$\mbox{每行取數的得分} = \mbox{被取走的元素值}\times{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<=${a}{_i}{_j}$<=1000
輸出格式:
輸出文件game.out僅包含1行,為一個整數,即輸入矩陣取數后的最大得分。
輸入輸出樣例
2 3 1 2 3 3 4 2
82
說明
NOIP 2007 提高第三題
吐槽
一道DP練手題,2007年時這題是靠高精才撐到那么高的難度的……本題數據范圍到$2^{90}$,long long不夠,C++11里的__int128對付這題簡直變態,最大$2^{128}$,於是這題松有……
常年被大神鄙視,RP積攢了好多,寫某些程序都自帶小常數。這題我11ms,在洛谷恐怕是rank1了吧,即使不是也是前十(這題不在大牛分站,看不了排名)。翻了6頁記錄,200ms以內的全用__int128,其他版本的高精度耗時從230ms到2000ms不等。
2019年1月21日19:51:35更新
當時洛谷人不多,評測機也就那么幾台,還都取了很有意思的名字,香港記者、Wallace(后面有沒有s來着)、光明牌冰磚、BanGdream……我高二一整年還連續打卡370天。現在人多是非多,以前一直存在但是一露頭就會被噴死的網絡暴力和機房慘案也大規模入侵這片凈土了,直接導致以前的樂趣了沒了,比如犇犇。堅持在洛谷打卡做題的一大原因就是能看到全國各地一大群一起奮斗的OIer。現在,當時同行的人都各自散去,報送的報送,簽約的簽約,高考的高考……新的一代進入,洛谷都快二十萬用戶了,卻也少了很多東西……洛谷一直在成長,我也在成長,可能這就是成長的代價?
今天懷舊,點進去洛谷題解里看了一下,恍然大悟,原來int128可以那么寫——
第一種(@顏偉業_C_Asm)

typedef struct _tword{ unsigned long long rah;//高64位 unsigned long long ral;//低64位 friend bool operator <(const _tword &a,const _tword &b){ if(a.rah<b.rah)return(1); if(a.rah>b.rah)return(0); return(a.ral<b.ral); } }tword;//這個結構體就是128位整數了 unsigned long long hbt=1; unsigned long long man32;//0~31位全1 unsigned long long man32h;//32~63位全1 void add(tword a,tword b,tword &res){ unsigned long long al=a.ral&man32; unsigned long long ah=(a.ral&man32h)>>32; unsigned long long bl=b.ral&man32; unsigned long long bh=(b.ral&man32h)>>32; //因為這里需要處理進位,所以我們要把128位拆成4個32位並把它們擴充至64位,這樣就能通過對結果的高32位的處理來加上進位 al+=bl; ah=ah+bh+((al&man32h)>>32); res.rah=a.rah+b.rah+((ah&man32h)>>32); res.ral=((ah&man32)<<32)|(al&man32); }//128位加法 void kuomul(unsigned long long a,unsigned long long b,tword &res){//計算64位×64位=128位 unsigned long long al=a&man32; unsigned long long ah=(a&man32h)>>32; unsigned long long bl=b&man32; unsigned long long bh=(b&man32h)>>32; //ah、al為a的高低32位,bh、bl為b的高低32位,則a*b=(ah*bh)<<32+(ah<<32)*bl+(bh<<32)*al+al*bl unsigned long long albl=al*bl; unsigned long long ahbh=ah*bh; unsigned long long albh=al*bh; unsigned long long ahbl=ah*bl; tword r1,r2,r3,r4; r1.rah=ahbh; r1.ral=0; r2.rah=0; r2.ral=albl; r3.ral=(albh&man32)<<32; r3.rah=(albh&man32h)>>32; r4.ral=(ahbl&man32)<<32; r4.rah=(ahbl&man32h)>>32; res.rah=0; res.ral=0; add(res,r1,res); add(res,r2,res); add(res,r3,res); add(res,r4,res);//把四項相加,得出結果 } void mul(tword a,int b,tword &res){//128乘int的運算 tword tmp; unsigned long long ah=a.rah*b; kuomul(a.ral,b,tmp); unsigned long long al=tmp.ral; res.rah=ah+tmp.rah; res.ral=al; } int mod10(tword &hint){//把128位hint除以10,並返回余數 unsigned long long eah=hint.rah / 10; unsigned long long mod=((hint.rah%10)<<32)|((hint.ral&man32h)>>32); unsigned long long low=hint.ral&man32; hint.rah=eah; unsigned long long eal=(mod/10)<<32; low=low|((mod%10)<<32); eal=eal|(low/10); hint.ral=eal; return(low%10); }
第二種(@ Youth丨吹雪)

1 struct int128 2 { 3 long long hig; 4 long long low; 5 };//定義int128 6 int n,m; 7 long long p=1e18;//作mod用 8 int128 ans,f[85][85][85],a[85][85]; 9 int128 max(int128 a,int128 b) 10 { 11 if(a.hig>b.hig) return a; 12 if(a.hig<b.hig) return b;//高位比較 13 if(a.low>b.low) return a; 14 if(a.low<b.low) return b;//低位比較 15 return a;//相等時還要返回一個值 16 } 17 int128 operator + (int128 a,int128 b)//重載運算符 18 { 19 int128 k; 20 k.low=0,k.hig=0; 21 k.low=a.low+b.low; 22 k.hig=k.low/p+a.hig+b.hig;//防止溢出 23 k.low%=p; 24 return k; 25 } 26 int128 operator * (int128 a,int b) 27 { 28 int128 k; 29 k.low=0,k.hig=0; 30 k.low=a.low*b; 31 k.hig+=k.low/p+b*a.hig;//與上同理 32 k.low%=p; 33 return k; 34 }
雖然不完備,但也可以啟發思路了。
解題思路
不算高精度,就是一道簡單的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; }