题解 P1005 【矩阵取数游戏】

· · 题解

几个月前本蒟蒻以为这题是个贪心,结果......可想而知

几个月后本蒟蒻发现它是个 区间dp 还想到了正解,结果楞是数组开小了一个单元,RE 了......

【分析】

其实虽然这题不是个贪心,但还是有一点贪心的思想

对于题目的描述,我可以看成有 n 个同学对每一行取数

且每行 m 个数中,得分的计算规则不变

那么,题目就是求这 n 个同学得分 ans_i 之和的最大值

由于每个同学的得分是相互独立的

所以,想要求得分最大值,就是对每个同学的得分最大值求和

ans=\sum_{i=1}^n ans_i

--

那么,接下来

我们考虑如何将每个同学的得分最大

对于一个区间 [i,j] 的得分最大值,我们记为 dp_{i,j}

那么,由题目得每次取数都必须从区间的开头或者结尾取数

也就是说 dp_{i,j}dp_{i,j-1}dp_{i+1,j} 之间有着必然的联系,转移方程肯定从这里入手

假设与 dp_{i,j-1} 有关的状态更优。那么,我们先考虑 dp_{i,j}dp_{i,j-1} 的关系

我们可以很清楚的知道,dp_{i,j} 是在区间 [i,j] 上取数的最大值,一共是 (j-i+1) 个数

dp_{i,j-1} 是在区间 [i,j-1] 上取数的最大值,一共是 (j-1-i-1)=(j-i-2) 个数

按照我们之前的假设,与 dp_{i,j-1} 的状态更优

那么,对于区间 [i,j] 的得分,显然是先取第 i 个数,再对后面的区间 [i,j-1] 取最大得分

那么,取第 i 个数的得分为 map_j \times 2^1

取后面的区间 [i,j-1] 的最大得分为 (\sum_{k=1}^{j-i+2} map_{c_k}\times 2^{k+1})_{max} , 其中 c_k 是区间[i,j-1]中的数,且互不相同

根据定义,我们又可以知道 dp_{i,j-1}=(\sum_{k=1}^{j-i+2} map_{c_k}\times 2^k)_{max}

={1\over 2}(\sum_{k=1}^{j-i+2} map_{c_k}\times 2^{k+1})_{max}

因此,联立上面式子,得出

如果 dp_{i,j-1} 的状态更优,那么 dp_{i,j}=dp_{i,j-1} \times 2 +map_j \times 2=2(dp_{i,j-1}+map_j)

同理,可以得出,如果 dp_{i+1,j} 的状态更优,那么,同样有 dp_{i,j}=2(dp_{i+1,j}+map_i)

因此,可以归纳出状态转移方程

dp_{i,j}=2 \times max(dp_{i,j-1}+map_j,dp_{i+1,j}+map_i)

--

你以为这就完了吗......

我们算一下答案的范围

**unsigned long long int** 根本存不下...... 手打高精吧,反正只有正整数加法,很仁慈了 这边本蒟蒻推荐用重载运算符 ~~自我这样整个代码看起来更加优雅~~ --- **【代码】** -- 那本蒟蒻就放代码了 ```cpp #include<cstdio> using namespace std; #define maxn(a,b) ((a>b)?a:b) inline int read(){ register int ans=0;register char c=getchar();register bool neg=0; while((c<'0')|(c>'9')) neg^=!(c^'-'),c=getchar(); while((c>='0')&(c<='9')) ans=(ans<<3)+(ans<<1)+c-'0',c=getchar(); return neg?-ans:ans; }//掺了黑科技的读入优化 int n,m; struct bignum{ int len,num[10]; bignum() { len=1; for(int i=0;i<10;i++) num[i]=0; }//构造函数 void equal(int x){ if(!x) return ; len=0; while(x>0){ num[len++]=x%100000; x/=100000; } }//赋值忘记怎么打了...... bool operator > (const bignum &x){ if(len^x.len) return len>x.len; for(int i=len-1;i>=0;i--) if(num[i]^x.num[i]) return num[i]>x.num[i]; return 0; }//重载大于号 bignum operator + (const bignum &x){ bignum y; for(int i=0;(i<len)|(i<x.len);i++){ y.num[i]+=num[i]+x.num[i]; if(y.num[i]>=100000) y.num[i]-=100000,y.num[i+1]++; } y.len=(len>x.len)?len:x.len; if(y.num[y.len]) y.len++; return y; }//重载加号 bignum operator + (const int &x){ bignum y; y.equal(x); return (*this)+y; }//重载加整数 void print(){ printf("%d",num[len-1]); for(int i=len-2;i>=0;i--){ int tmp=num[i]; for(int j=10000;j;j/=10) putchar(tmp/j+'0'),tmp%=j; } }//输出函数 }; bignum init(){ int map[81]; for(int i=1;i<=m;i++) map[i]=read(); bignum dp[82][82]; for(int l=1;l<=m;l++) for(int s=1;s+l-1<=m;s++){ int e=s+l-1; dp[s][e]=maxn(dp[s][e-1]+map[e],dp[s+1][e]+map[s]); dp[s][e]=dp[s][e]+dp[s][e]; } return dp[1][m]; }//个人对于空间尽量小的蜜汁癖好 int main(){ n=read(); m=read(); bignum ans; for(int i=0;i<n;i++) ans=ans+init(); ans.print(); return 0; } ```