洛谷 P1005 矩阵取数游戏
题目大意
现在有一个\(n\times m\)的矩阵,现在要从中取数,每次取数只能取走每行的第一个或者最后一个,并且每次取数都是对每一行操作的,也就是说每一次取数会从每一行取一个,总共\(n\)个,现在让你取\(m\)次,也就是说,\(m\)次之后整个矩阵都取完了,让你计算最大得分。
得分计算方法:对于第\(i\)次取数中取出的数字\(x\),它的得分是\(x\times 2^i\)(注意,一次取数取了\(n\)个数字!),然后请你求出最大的得分是多少...
样例解释
我觉得这个样例一开始有点难以理解,因为我开始没看懂题目,于是这里解释一下:
第一次取走第一行的\(1\)以及第二行的\(2\),得分是\(1\times 2^1+2\times 2^1=6\)
第二次取走第一行的\(2\)以及第二行的\(3\),得分是\(2\times 2^2+3\times 2^2=20\)
第三次取走第一行的\(3\)以及第二行的\(4\),得分是\(3\times 2^3+4\times 2^3=56\)
所以总得分是:\(6+20+56=82\)
思路
显然的区间\(dp\)啊!因为每一行的取值不会影响其他行的计算,所以我们对于每一行取计算它取数\(m\)次的最大值,最后再加起来就好了。我们设计状态\(dp_{l,r,k}\)表示在第\(k\)行,对于区间\([l,r]\)的最大得分是多少,然后可以得出状态转移方程:
这个转移方程说的是什么呢?就是如果我们取了第\(k\)行的最左边,那么我们就把当前区间的左端点加上\(1\),并且加上最左边的那个值;如果取右边,则同理,缩小右端点并且加上最右边的那个值。最后的那个\(*2\)是什么意思?读题,因为每次取数的得分是要乘上\(2^i\)的,所以我们每一次都乘上\(2\),那么自然的,每一次乘上的就有了这次的\(*2\)及之前一共乘的\(2^{i-1}\),乘一下就是\(2_i\)了。
显然,此处的\(k\)是枚举的,且在状态转移中是不变的,所以这一维便可以优化掉,也就是\(dp_{i,j}\),只需要在计算每一行时\(memset\)为\(0\)就好了。
关于精度问题的话,显然\(long\ long\)是远远不够的,那么就是高精度了,但是您觉得我是那种写高精度的人吗?__int128直接上,嘿嘿嘿。(\(128\)位是够得,注意,\(NOIp\)系列比赛中是不可使用的,老老实实写高精度吧/fad)
\(ACcode:\)
#include<bits/stdc++.h>
#define F(i,l,r) for(register int i=l;i<=r;i++)
#define D(i,r,l) for(register int i=r;i>=l;i--)
#define ll long long
#define mem(a,b) memset(a,b,sizeof(a))
#define p_b push_back
#define m_p make_pair
#define il inline
#define int __int128
//define一下,__int128太长了:-(
const int INF=0x3f3f3f3f;
using namespace std;
int fr() {
char ch=getchar();
int num=0,k=1;
while((ch>'9'||ch<'0')&&ch!='-') ch=getchar();
if(ch=='-') k=-1,ch=getchar();
while(ch<='9'&&ch>='0') num=num*10+ch-'0',ch=getchar();
return num*k;
}
void write(int x) {
if(x>9) write(x/10);
putchar(x%10+'0');
}
int n,m;
int dp[85][85],ans,mp[85][85];
signed main() {
n=fr(),m=fr();
F(i,1,n) F(j,1,m) mp[i][j]=fr();
F(i,1,n) {
F(len,0,m) {
F(l,1,m) {//区间dp套路写法/xyx
int r=l+len;
if(r>m) break;
dp[l][r]=max(dp[l+1][r]+mp[i][l],dp[l][r-1]+mp[i][r])*2;
}
}
ans+=dp[1][m];
mem(dp,0);//不要忘记清0!
}
write(ans);
return 0;
}

浙公网安备 33010602011771号