P1005 矩阵取数游戏 (60)

DP

题目链接:https://www.luogu.org/problem/show?pid=1005

题目大意:

有一个n * m的矩阵,玩一个取数游戏,每次从1到n行取行首或行尾乘2的

取的次数次方为这次取数的得分,求得分最大值。

 

A:每次取每行最大不就行了?定义f[i][j]为取到第i次,到第j行的max。

 

    for(int i = 1;i <= m;++ i){
        for(int j = 1;j <= n;++ j){
            f[i][j] = maxx(f[i][j - 1] * a[j][s],f[i][j - 1] * a[j][e]);
        }
    }

 

B:还有个次数次方呢...

A:那每次取最小

B:你怎么知道后面一定会更优?

A:……那怎么办?

B:为什么不可以用你说的方法呢?

A:因为我这个方法不能保证这个状态下是最优的。

B:因为不论取左边还是取右边都会影响后面的状态。

B:这个问题熟不熟悉?

A:嗯,方格取数!!当时也是分开走会影响后面的状态!!

B:怎么处理的来着?

A:加维度!!

END.

这个题第一眼看上去感觉好简单,

然后发现思路明明是贪心啊QAQQ,

然后感觉好难,

但是不要紧,一点点分析,把大问题转化成小问题,

比如这个题,

要取m次,先看取1次?要取n行,先看取1行?要取1行,先看只有1个,2个,3个数的方法?

1个就不说了QAQQ,

如果有两个数,a b为一行,

如果取走b剩下a,就加上b * ...直接和另一种情况取min

如果有3个数 a b c,

可以取走a or c,剩下b,

即b之前的状态可能是a b 或是 b c,

这两种情况取走a或c都有可能成为答案,那就都记下来,

4个数 a b c d,一样的,

在更新剩下b c的状态的时候,

他之前的状态也有两种:a b c或者 b c d,

两种情况取走a或取走d也都可以成为答案,都记录,

咦?有没有发现a b c的情况我们之前明明已经更新过了?(取3个数的时候)

也就是说,b c在所在的位置,所组成的区间,我们已经更新过了?

可以记忆化,那一定可以dp,

那怎么设计状态呢?

要存一个当前取的次数,当前取到哪一行,还有??

看上面上面上面!!不是已经知道b c更新过了么?

也就是说怎么存b c这段区间呢?

存一段区间可以存起点终点或者起点长度,

随便选一个好了,

设计状态:f[i][j][s][e]:第i次取,到第j行,s到e区间的答案

Codes:

 

    f[i][j][s][e] = maxx(f[i][j][s - 1][e] + a[j][s - 1] * (1 << i),f[i][j][s][e + 1] + a[j][e + 1] * (1 << i));

 

哇哦4维啊好长,有没有发现我在用 i 更新 i ?在用 j 更新 j ?

扔掉扔掉,i可以边输入边处理(这里就不写了)

这样状态定义可以是这样;

f[i][s][e]:到第i行,s到e区间的答案。

就是这样啦~

       for(int i = 1;i <= n;++ i){
         for(int s = 1;s <= m;++ s){
             for(int e = m;e >= s;-- e){
                  int t = m - (e - s) - 1;
                  f[i][s][e] = maxx(f[i][s - 1][e] + a[i][s - 1] * (1 << t),
                                  f[i][s][e + 1] + a[i][e + 1] * (1 << t));
            }
        }
    }

(其中t是第t次取数,可以通过s和e来求)

大神还有一种写法:

  for(int i = 1;i <= n;i ++)
         for(int j = 1;j <= m;j ++)
              for(int k = m;k >= j;k --){
                  int t = m - (k - j);
                  dp[i][j + 1][k] = max(dp[i][j + 1][k],dp[i][j][k] + map[i][j] * (1 << t));
                  dp[i][j][k - 1] = max(dp[i][j][k - 1],dp[i][j][k] + map[i][k] * (1 << t));
              }

然后最终答案很明显是每行答案之和啦~(当时我这里写错QAQQ忘了统计。。。)

Codes:

 1 #include<iostream>
 2 #include<cstring>
 3 #include<cstdio>
 4 #include<algorithm>
 5 #define ll long long
 6 using namespace std;
 7 const int N = 80 + 5;
 8 ll a[N][N],f[N][N][N],n,m;
 9 ll maxx(ll x,ll y){if(x > y) return x;return y;}
10 int main(){
11     scanf("%d%d",&n,&m);
12     for(int i = 1;i <= n;++ i){
13         for(int j = 1;j <= m;++ j){
14             scanf("%d",&a[i][j]);
15         }
16     }
17     for(int i = 1;i <= n;++ i){
18         for(int s = 1;s <= m;++ s){
19             for(int e = m;e >= s;-- e){
20                 int t = m - (e - s) - 1;
21                 f[i][s][e] = maxx(f[i][s - 1][e] + a[i][s - 1] * (1 << t),f[i][s][e + 1] + a[i][e + 1] * (1 << t));
22             }
23         }
24     }
25     ll ans = 0;
26     for(int i = 1;i <= n;++ i){
27         ll ans1 = 0;
28         for(int j = 1;j <= m;++ j)
29             ans1 = maxx(ans1,f[i][j][j] + a[i][j] * (1 << m));
30         ans += ans1;
31     }
32     cout << ans << '
';
33     return 0;
34 }

 还有一种状态定义的方式f[i][j][a][b] :

表示在第i次,取到第j行,左边取了a个,右边取了b个的max,

这个更加直接应该更容易想到,(虽然我没有想到QAQQ)

左边取a个上一个状态是左边取了a - 1个,右边同理。

i,j同样可以去掉。

注意数据范围,需要用高精,这里我没有加。60。

这个题就是这样啦。

MAS:

先知道怎样递推再定义状态

保证状态的唯一性

不会写就加维度加维度加维度

原文地址:https://www.cnblogs.com/Loizbq/p/7742366.html