题目大意
给你一个 n×nn \times nn×n 的矩阵,每个点有两个值,代表当前点物品的累加的数量和累加的价值,累加的价值就是说你到达了这个点,你所有物品的价值都会累加上这个价值,累加的数量表示你到达了这个点你的物品数量会加上这个值,你每次只能向下或者向右走。问你从 (1,1)(1, 1)(1,1) 走到 (n,n)(n, n)(n,n) 所获得的总价值最大是多少,总价值为数量乘以价值。
1≤n≤1001 \leq n \leq 1001≤n≤100
解题思路
dpdpdp
由于每个点有两个状态,我们将每个点的状态用一个 vectorvectorvector 存起来。
dp[i][j]=dp[i−1][j]+dp[i][j−1]dp[i][j] = dp[i-1][j] + dp[i][j-1]dp[i][j]=dp[i−1][j]+dp[i][j−1]
此时,走到 i,ji, ji,j 位置的答案就是 dp[i][j]dp[i][j]dp[i][j] 里面每个点的二维乘积
但是这样的复杂度是 O(T×2n−1)O(T \times 2^{n-1})O(T×2n−1)
我们发现有一些状态是必不可能成为答案的,我们需要将这些状态给即使排除。比如 fii≤fij && sei≤sejfi_i \leq fi_j \ \&\& \ se_i \leq se_jfii≤fij && sei≤sej
那么, iii 这个点永远没有 jjj 这个点优,所以我们要即使排除这个状态,以减少复杂度。
每次我们在合并的时候归并一下,把每个点的 vectorvectorvector 当做一个单调栈,当出现上述情况时及时排除不可能的答案。
Code
#include <bits/stdc++.h>
#define ll long long
#define qc ios::sync_with_stdio(false); cin.tie(0);cout.tie(0)
#define fi first
#define se second
#define PII pair<int, int>
#define PLL pair<ll, ll>
#define pb push_back
using namespace std;
const int MAXN = 2e5 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const ll mod = 1e9 + 7;
inline int read()
{
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int n;
ll a[107][107];
ll b[107][107];
vector<PLL> v[107][107];
void solve(){
cin >> n;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++){
cin >> a[i][j];
}
}
for (int i = 1; i <= n; ++i){
for (int j = 1; j <= n; ++j){
cin >> b[i][j];
}
}
for (int i = 1; i <= n; ++i){
for (int j = 1; j <= n; ++j){
if(i == 1 && j == 1){
v[i][j].pb({a[i][j], b[i][j]});
continue;
}
ll num = a[i][j];
ll add = b[i][j];
int p1 = 0;
int sz1 = v[i-1][j].size();
int p2 = 0;
int sz2 = v[i][j-1].size();
int cnt = -1;
while(p1 < sz1 && p2 < sz2){
int f1 = v[i-1][j][p1].fi;
int s1 = v[i-1][j][p1].se;
int f2 = v[i][j-1][p2].fi;
int s2 = v[i][j-1][p2].se;
if(f1 <= f2){
while(cnt != -1 && v[i][j][cnt].fi <= f1 + num && v[i][j][cnt].se <= s1 + add){
cnt--;
v[i][j].pop_back();
}
v[i][j].pb({f1 + num, s1 + add});
cnt++;
p1++;
}
else{
while(cnt != -1 && v[i][j][cnt].fi <= f2 + num && v[i][j][cnt].se <= s2 + add){
cnt--;
v[i][j].pop_back();
}
cnt++;
v[i][j].pb({f2 + num, s2 + add});
p2++;
}
}
while(p1 < sz1){
int f1 = v[i-1][j][p1].fi;
int s1 = v[i-1][j][p1].se;
while(cnt != -1 && v[i][j][cnt].fi <= f1 + num && v[i][j][cnt].se <= s1 + add){
cnt--;
v[i][j].pop_back();
}
v[i][j].pb({f1 + num, s1 + add});
cnt++;
p1++;
}
while(p2 < sz2){
int f2 = v[i][j-1][p2].fi;
int s2 = v[i][j-1][p2].se;
while(cnt != -1 && v[i][j][cnt].fi <= f2 + num && v[i][j][cnt].se <= s2 + add){
cnt--;
v[i][j].pop_back();
}
cnt++;
v[i][j].pb({f2 + num, s2 + add});
p2++;
}
}
}
ll ans = 0;
for(auto [x, y] : v[n][n]){
ans = max(ans, x * y);
}
cout << ans << "\n";
for (int i = 1; i <= n; ++i){
for (int j = 1; j <= n; ++j){
v[i][j].clear();
}
}
}
int main()
{
#ifdef ONLINE_JUDGE
#else
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
qc;
int T;
cin >> T;
// T = 1;
while(T--){
solve();
}
return 0;
}

这篇博客介绍了一个使用动态规划求解矩阵最优化问题的算法。问题设定是在一个n×n的矩阵中,每个单元格包含两个值,分别表示物品的累加数量和累加价值。目标是从左上角走到右下角,使得路径上的物品总价值最大。通过维护一个单调栈排除次优状态,优化了复杂度,最终实现了寻找最优解的高效算法。
1191

被折叠的 条评论
为什么被折叠?



