解题思路:动态规划

注意题目的描述,每次取数时须从每行各取走一个元素,共 n 个,m 次后取完矩阵所有元素,计算m次取数的得分之和。实际上这种取法可以转化下,每次取完一行后再取下一行,得到的结果没有任何差别。

因此可以对每一行单独处理,这时就用到了动态规划的思想,这是典型的区间dp问题。由于每次取数必须从首尾两端取,因此我们可以令dp[l][r]表示剩余区间为l-r时的最大得分,由此可以得到状态转移方程。

dp[l][r] = max(dp[l - 1][r] + (a[l - 1] << i) , dp[l][r + 1] + (a[r + 1] << i)),其中i表示第i次取数

需要注意的是,虽然题目要求将结果对于1e9+7取模,但实际上动态规划的过程中是不能取模的,否则进行动态转移时进行大小判断可能出错。而 m <= 100,val <= 1000,因此最终计算结果的范围可以达到1000 * 2^101,使用long long明显不够,必须使用__int128。

除了__int128之外,我还有另一种思路。第一次取的数乘以2,第二次取的数乘以4,第三次取的数乘以8……由于每次新取的数要乘以上一个取的数所乘倍数的2倍,因此dp的时候,可以将上一次dp的值除以2,再加上当前取的数。当然直接除以2可能产生小数不可取,因此这里要用到逆元。具体的做法我还没想好,有兴趣的朋友可以用这个方法试一试,然后私信我。

下面贴出代码。

#include <iostream>
#include <vector>
using namespace std;

int main() {
    const int mod = 1e9 + 7;
    int m, n;
    cin >> n >> m;
    vector<int> a(m);
    long long ans = 0;
    for (int i = 0; i < n; ++i) {
        for (int& j : a) {
            cin >> j;
        }
        vector<vector<__int128>> dp(m, vector<__int128>(m, 0));
        //dp[l][r]表示剩余区间为l-r时的最大得分
        int cnt = 0;//计算当前轮乘以2的次数
        for (int k = m - 2; k >= 0; --k) { //区间从大到小遍历
            ++cnt;
            for (int r = k; r < m; ++r) {
                int l = r - k;
                if (l == 0) {
                    dp[l][r] = dp[l][r + 1] + ((__int128)a[r + 1] << cnt);
                } else if (r == m - 1) {
                    dp[l][r] = dp[l - 1][r] + ((__int128)a[l - 1] << cnt);
                } else {
                    dp[l][r] = max(dp[l - 1][r] + ((__int128)a[l - 1] << cnt),
                                   dp[l][r + 1] + ((__int128)a[r + 1] << cnt));
                }
            }
        }
        __int128 res = 0;
        for(int i = 0; i < m; ++i) {
            res = max(res, dp[i][i] + ((__int128)a[i] << m));
        }
        ans += res % mod;
    }
    cout << ans % mod;
}