题目描述
帅帅经常跟同学玩一个矩阵取数游戏:对于一个给定的${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)
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
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丨吹雪)
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
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; }