解题思路:动态规划
注意题目的描述,每次取数时须从每行各取走一个元素,共 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;
}



京公网安备 11010502036488号