1.题目介绍
题目传送门
翻译后
D最大乘积和
每测试2秒的时间限制
每测试256兆字节的内存限制
输入标准输入
输出标准输出
给出了两个长度为n的整数数组a和b。
最多可以反转阵列a的一个子阵列(连续子段)。
您的任务是反转这样一个子数组,该子数组的和∑i=1nai⋅bi最大化。
输入
第一行包含一个整数n(1)≤N≤5000).
第二行包含n个整数a1,a2,…,an(1≤a[i]≤107).
第三行包含n个整数b1,b2,…,bn(1≤b[i]≤107).
输出
打印单个整数-最多反转一个子数组(连续子段)后的最大可能总和。
例子
输入拷贝
5.
2 3 2 1 3
1 3 2 4 2
输出拷贝
29
输入拷贝
2.
13 37
2 4
输出拷贝
174
输入拷贝
6.
1 8 7 6 3 6
5 9 6 8 8 6
输出拷贝
235
注
在第一个示例中,可以反转子阵列[4,5]。然后a=[2,3,2,3,1]和2⋅1+3⋅3+2⋅2+3⋅4+1⋅2=29.
在第二个示例中,不需要使用反向操作。13⋅2+37⋅4=174.
在第三个示例中,可以反转子阵列[3,5]。然后a=[1,8,3,6,7,6]和1⋅5+8⋅9+3⋅6+6⋅8+7⋅8+6⋅6=235.
2.解题思路
很明显的区间dp,枚举翻转区间的长度len[2 -> n],再枚举起点s[1, n - len +1], 最后进行计算。
先计算出未反转前的前缀和,再对翻转区间进行区间dp
设当前翻转区间[i, j], 它是由上一区间[i + 1, j - 1] + 上区间端点 i, j翻转转移而来的,从而设计出状态转移方程
c[i][j] = c[i + 1][j - 1] + a[i] * b[j] + a[j] * b[i];
3.参考代码
#include<bits/stdc++.h>
#define x first
#define y second
#define rep(i, a, b) for (i = (a); i <= (b); ++i)
using namespace std;
typedef pair<int, int> P;
typedef long long ll;
const int N = 5010;
const int mod = 1e9 + 7;
int n;
ll a[N], b[N];
ll dp[N]; // 统计前缀和
ll c[N][N]; // 统计翻转后的区间和
int main()
{
scanf("%d", &n);
ll ans = 0;
for (int i = 1; i <= n; i ++ ) scanf("%lld", &a[i]);
for (int i = 1; i <= n; i ++ ) {
scanf("%lld", &b[i]);
//初始化
dp[i] = dp[i - 1] + a[i] * b[i];
c[i][i] = a[i] * b[i];
}
ans = dp[n];
//暴力做法 O(n^3)
/*
for (int len = 2; len <= n; ++len) { // 枚举翻转区间长度
for (int i = 1; i <= n - len + 1; ++i) { // 枚举起点
int j = i + len - 1; // 终点
ll u = dp[i - 1] + dp[n] - dp[j]; //翻转区间以外的总和
//cout << u<< endl;
//依次枚举翻转区间内所有的点
for (int k = i, v = j; k <= j; ++k, --v) {
u += a[k] * b[v];
}
ans = max(ans, u);
}
} */
//区间优化
for (int len = 2; len <= n; ++len) {
for (int i = 1; i <= n - len + 1; ++i) {
int j = i + len - 1;
// 当前的翻转区间[i, j]是由上一翻转区间[i + 1, j - 1] + 当前区间端点翻转 (a[i] * b[j] + a[j] * b[i])
c[i][j] = c[i + 1][j - 1] + a[i] * b[j] + a[j] * b[i];
ll u = dp[i - 1] + dp[n] - dp[j];
//cout << u<< endl;
u += c[i][j];
ans = max(ans, u); //统计最大值
}
}
cout << ans << endl;
return 0;
}