|
|
|
@ -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)
|
|
|
|
|