题目描述
帅帅经常跟同学玩一个矩阵取数游戏:对于一个给定的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行,为一个整数,即输入矩阵取数后的最大得分。
输入输出样例
说明
NOIP 2007 提高第三题
思路:
很显然,可以发现这样的一个规律:
这一整个矩形我们在进行手动模拟的时候可以发现:
我们可以把取一整个矩形的最大值分割成取每一行的最大值然后累计的和就是答案。
因为很显然可以发现每一行都是互不影响独立存在的,比如,上面的图像的最大值显然是:
第一次取:1*2^1+4*2^1+7*2^1 ①
第二次取:2*2^2+5*2^2+8*2^2 ②
第三次取:3*2^3+6*2^3+9*2^3 ③
ans=①+②+③=( 1*2+2*22+3*23 )+( 4*2+5*22+6*23 )+(7*2+8*22+9*23 )
也就把一个大问题分割成了许多行的小问题。然后就是经典的区间DP了。
f[l][r]=max(f[l+1][r]+map[i][l]*2m-L+1,f[l][r-1]+map[i][r]*2m-L+1);
特别注意的一点:这个题目的数据范围是要用高精的。
60分的不加高精的代码:
#include<cmath> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; int n,m; long long ans; long long map[81][81],f[81][81]; long long pow(int x,int y){ long long bns=1; for(int i=1;i<=y;i++) bns*=x; return bns; } int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) scanf("%d",&map[i][j]); for(int i=1;i<=n;i++){ memset(f,0,sizeof(f)); for(int j=1;j<=m;j++) f[j][j]=map[i][j]*pow(2,m); for(int L=2;L<=m;L++) for(int l=1;l<=m-L+1;l++){ int r=l+L-1; f[l][r]=max(f[l+1][r]+map[i][l]*pow(2,m-L+1),f[l][r-1]+map[i][r]*pow(2,m-L+1)); } ans+=f[1][m]; } cout<<ans; }
AC代码:鬼畜操作(好吧我承认,只是因为我懒才用的,懒得写高精。。。~\(≧▽≦)/~)
#include<bits/stdc++.h> using namespace std; int n,m,a[105]; __int128 f[105][105],t[105],Ans;//__int128大法好 NOI系列赛事不推荐使用 应该会爆0 inline int read() { int s=0,f=1; char c=getchar(); while(c<‘0‘ || c>‘9‘) { if(c==‘-‘) f=-1; c=getchar(); } while(c>=‘0‘ && c<=‘9‘) { s=s*10+c-48; c=getchar(); } return s*f; }//快读 void Print(__int128 x) { if(x==0) return; else if(x) Print(x/10); putchar(x%10+‘0‘); }//比较玄学的输出 我记得本蒻菜很久之前用过这种输出 忘记那道题目了 int main() { n=read(); m=read(); t[0]=1; for(int i=1;i<=m;i++) t[i]=t[i-1]*2;//创建一个2的次方的表,以便于运算 for(int X=1;X<=n;X++) { for(int Y=1;Y<=m;Y++) a[Y]=read(); memset(f,0,sizeof(f)); for(int i=1;i<=m;i++) { for(int j=m;j>=i;j--) f[i][j]=max(f[i-1][j]+t[m-j+i-1]*a[i-1],f[i][j+1]+t[m-j+i-1]*a[j+1]); }//转移方程 __int128 MaxN=-1; for(int i=1;i<=m;i++) MaxN=max(MaxN,f[i][i]+t[m]*a[i]);//求出整行的最大得分 Ans+=MaxN;//再加上这一行的分数 } if(Ans==0) printf("0");//防止0的直接return而不输出 else Print(Ans);//输出,结束 return 0; }