main
黄海 2 years ago
parent 35fa111200
commit 7fd07f4cdb

@ -1,33 +1,28 @@
#include <bits/stdc++.h>
using namespace std;
const int N = 125;
int n; // 边长
int ans = INT_MIN;
int a[N][N]; // 记录矩形
int s[N]; // s[k]表示[i,j]行范围内第k列的前缀和
int dp[N];
const int N = 150;
int a[N][N];
void getDp() {
memset(dp, 0, sizeof dp);
for (int i = 1; i <= n; i++) {
dp[i] = max(s[i], dp[i - 1] + s[i]); // 只有当前列或者加上上前面val最大的矩形
ans = max(ans, dp[i]);
}
}
int dp[N]; // f[j]表示压缩的矩形第j列的值
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
for (int j = 1; j <= n; j++) {
cin >> a[i][j];
a[i][j] += a[i - 1][j]; // 按列计算前缀和
}
for (int i = 1; i <= n; i++) { // 枚举起始行
memset(s, 0, sizeof s); // 重置前缀数组
for (int j = i; j <= n; j++) { // 枚举末尾行
for (int k = 1; k <= n; k++) // 枚举列
s[k] += a[j][k];
getDp();
int ans = INT_MIN; // 预求最大,先设最小
for (int i = 1; i <= n; i++) // 最底下一行
for (int k = 1; k <= i; k++) { // 往上k行
memset(dp, 0, sizeof dp);
for (int j = 1; j <= n; j++) { // 第j列
int s = a[i][j] - a[i - k][j]; // 求压缩的矩形第j列的值
dp[j] = max(dp[j - 1] + s, s); // 动态规划
ans = max(ans, dp[j]); // 更新答案
}
}
}
cout << ans << endl;
cout << ans << endl; // 愉快AC
return 0;
}

@ -330,135 +330,95 @@ int main() {
```
**$O(N^3)$算法**【选读】
该题算是$P1115$ 最大子段和的一个升级版,其实思想差不多,都是$DP$,只不过该题需要先进行一个 **矩阵压缩** 即二维变一维。
> **引子**
给出一段序列,选出其中连续且非空的一段使得这段和最大。
第一行是一个正整数$N$,表示了序列的长度。($N<=200000$)
**矩阵压缩**
这是 $P1115$ 最大子段和 的描述,也就是本题的一维版本。
假设有一个矩阵:
```cpp {.line-numbers}
-5 6 4
1 -2 6
2 1 -3
```
如何对它进行压缩呢,其实不难,类比一下:如果我们把一行看做一个数,这里看做三个数$a,b,c$,那么将这三个相邻数的进行不同的组合,将这个新的组合视为一个新的数,这就是进行压缩处理,例如$a,b,c$可以组合为
$\{[a],[ab],[abc],[b],[bc],[c]\}$,而矩阵压缩也类似。
> $DP$方程:`dp[i]=max(dp[i-1]+a[i],a[i])` $a[i]$表示这个数列的第$i$项。
先设置一个变量$mx$用于保存 **压缩后的一维数组的最大子序列和**。
那么我们如何来处理这一题呢?
- **① 第一次我们取第一行**
```cpp {.line-numbers}
-5 6 4
```
则其最大子序列和为$10$$mx=10$。
我们可以考虑将矩形压缩成一维,比如处理一个$2$行的矩形时,将$a [i][j]$与$a[i-1][j]$相加,成为一个新的数组$f[n]$,再使用上述代码进行动态规划,找出局部最优解。
- **② 第二次取第一二行**
```cpp {.line-numbers}
-5 6 4
1 -2 6
```
注意现在开始是矩阵压缩的 **精髓**,我们将每一列的数进行相加,将多行变为一行。
第一列:$-5+1=-4$
第二列:$6+(-2)=4$
第三列:$4+6=10$
那如何来快速将矩形折叠呢?
所以压缩后的一维数组为:
```cpp {.line-numbers}
-4 4 10
```
则其最大子序列和为$14$$mx=14$。
我们可以选择 **前缀和**
简单来说,就是在输入的时候,再次加上$a[i-1][j]$,这样可以用减法来快速表示压缩的矩形。
- **③ 第三次取第一二三行**
具体代码如下:
```cpp {.line-numbers}
-5 6 4
1 -2 6
2 1 -3
cin >> n;
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j){
cin >> a[i][j];
a[i][j]+=a[i-1][j];//根据前缀和定义处理
}
```
对每一列进行压缩:
第一列:$-5+1+2=-2$
第二列:$6+(-2)+1=5$
第三列:$4+6+(-3)=7$
所以压缩后的一维数组为:
用样例来表示,输入的是:
```cpp {.line-numbers}
-2 5 7
0 -2 -7 0
9 2 -6 2
-4 1 -4 1
-1 8 0 -2
```
则其最大子序列和为$12$$mx=14$。
**- ④ 第四次取第二行:**
在经过前缀和处理之后,输出的是这个:
```cpp {.line-numbers}
1 -2 6
0 -2 -7 0
9 0 -13 2
5 1 -17 3
4 9 -17 1
```
则其最大子序列和为$6$$mx=14$。
可以模拟一下,$a[i][j] - a[i-k][j]$正好是以$i$为最下面一行,往上$k$行的压缩结果,这就很方便地表示了压缩后的矩形
**- ⑤ 第五次取第二三行:**
```cpp {.line-numbers}
1 -2 6
2 1 -3
```
**那又怎么循环找出各行为最下一行,不同行数的矩阵最大值呢?**
我用$i$表示以$i$为最下一行,$k$表示向上$k$行,代码如下:
对每一列进行压缩:
第一列:$1+2=3$
第二列:$-2+1=-1$
第三列:$6+(-3)=3$
所以压缩后的一维数组为:
```cpp {.line-numbers}
3 -1 3
```
for(i=1;i<=n;i++){
for(k=1;k<=i;k++){
则其最大子序列和为$5$$mx=14$。
**- ⑥ 第六次取第三行:**
```cpp {.line-numbers}
2 1 -3
}
}
```
$k<=i$,保证了$i-k>=0$。
则其最大子序列和为$3$$mx=14$。
最后求得这个矩阵最大的子矩阵和为$14$
也就是第一二行的三四列
```cpp {.line-numbers}
6 4
-2 6
```
那再次循环,运用第一个代码的简单变形,可以求出以$i$为最下一行,向上$k$行的矩形最大值,多次更新$ans$,愉快$AC$。
**$Code$**
```cpp {.line-numbers}
#include <bits/stdc++.h>
using namespace std;
const int N = 150;
int a[N][N];
const int N = 330;
int n;
int a[N][N], s[N][N], dp[N];
int res = INT_MIN;
int dp[N]; // f[j]表示压缩的矩形第j列的值
int main() {
int n;
cin >> n;
// 前缀和(竖直方向)
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++) {
cin >> a[i][j];
s[i][j] = s[i - 1][j] + a[i][j]; // 一维前缀和,这可不是二维前缀和
a[i][j] += a[i - 1][j]; // 按列计算前缀和
}
// 降维变成一维dp
for (int i = 0; i <= n - 1; i++) // 枚举左上角
for (int j = i + 1; j <= n; j++) // 枚举右下角
for (int k = 1; k <= n; k++) { // 枚举每一列
dp[k] = max(s[j][k] - s[i][k], dp[k - 1] + s[j][k] - s[i][k]);
res = max(res, dp[k]);
int ans = INT_MIN; // 预求最大,先设最小
for (int i = 1; i <= n; i++) // 最底下一行
for (int k = 1; k <= i; k++) { // 往上k行
memset(dp, 0, sizeof dp);
for (int j = 1; j <= n; j++) { // 第j列
int s = a[i][j] - a[i - k][j]; // 求压缩的矩形第j列的值
dp[j] = max(dp[j - 1] + s, s); // 动态规划
ans = max(ans, dp[j]); // 更新答案
}
// 输出
cout << res << endl;
}
cout << ans << endl; // AC
return 0;
}
```
#### [$P2004$ 领地选择](https://www.luogu.com.cn/problem/P2004)

Loading…
Cancel
Save