diff --git a/TangDou/Topic/PrefixAndSuffix/P1719_2.cpp b/TangDou/Topic/PrefixAndSuffix/P1719_2.cpp index 9921878..aaa8ceb 100644 --- a/TangDou/Topic/PrefixAndSuffix/P1719_2.cpp +++ b/TangDou/Topic/PrefixAndSuffix/P1719_2.cpp @@ -1,28 +1,29 @@ #include using namespace std; +const int N = 310; -const int N = 330; -int n; int a[N][N], s[N][N], dp[N]; -int res = INT_MIN; +int n, ans; int main() { 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]; - s[i][j] = s[i - 1][j] + a[i][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]); + for (int j = 1; j <= n; j++) + for (int i = 1; i <= n; i++) + a[i][j] += a[i - 1][j]; + + for (int i = 1; i <= n; i++) { + for (int k = 1; k <= i; k++) { + for (int j = 1; j <= n; j++) { + int gap = a[i][j] - a[i - k][j]; + dp[j] = max(dp[j - 1] + gap, gap); + ans = max(dp[j], ans); } - // 输出 - cout << res << endl; + } + } + cout << ans; return 0; -} +} \ No newline at end of file diff --git a/TangDou/Topic/【前缀和与差分】题单.md b/TangDou/Topic/【前缀和与差分】题单.md index 2e0bcfd..fd9a525 100644 --- a/TangDou/Topic/【前缀和与差分】题单.md +++ b/TangDou/Topic/【前缀和与差分】题单.md @@ -330,34 +330,102 @@ int main() { ``` **$O(N^3)$算法**【选读】 -> **引子**: -> 给出一段序列,选出其中连续且非空的一段使得这段和最大。第一行是一个正整数$N$,表示了序列的长度。($N<=200000$) -> -这是 $P1115$最大子段和 的描述,也就是本题的一维版本。 +该题算是$P1115$ 最大子段和的一个升级版,其实思想差不多,都是$DP$,只不过该题需要先进行一个 **矩阵压缩** , 即二维变一维。 -$DP$方程:`dp[i]=max(dp[i-1]+a[i],a[i])` $a[i]$ 表示这个数列的第$i$项。 +**矩阵压缩** +假设有一个矩阵: ```cpp {.line-numbers} -#include -using namespace std; -const int N = 200100; -int ans = INT_MIN; -int n; -int a[N] , dp[N]; - -int main(){ - cin>>n; - for(int i=1;i<=n;i++){ - cin>>a[i]; - dp[i]=max(dp[i-1]+a[i],a[i]); - ans=max(ans,dp[i]); - } - printf("%d\n",ans); - return 0; -} +-5 6 4 +1 -2 6 +2 1 -3 +``` +如何对它进行压缩呢,其实不难,类比一下:如果我们把一行看做一个数,这里看做三个数$a,b,c$,那么将这三个相邻数的进行不同的组合,将这个新的组合视为一个新的数,这就是进行压缩处理,例如$a,b,c$可以组合为 +$\{[a],[ab],[abc],[b],[bc],[c]\}$,而矩阵压缩也类似。 + +先设置一个变量$mx$用于保存 **压缩后的一维数组的最大子序列和**。 + +- **① 第一次我们取第一行** +```cpp {.line-numbers} +-5 6 4 +``` +则其最大子序列和为$10$,$mx=10$。 + +- **② 第二次取第一二行** +```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$。 + + +- **③ 第三次取第一二三行** + +```cpp {.line-numbers} +-5 6 4 +1 -2 6 +2 1 -3 ``` +对每一列进行压缩: +第一列:$-5+1+2=-2$ +第二列:$6+(-2)+1=5$ +第三列:$4+6+(-3)=7$ +所以压缩后的一维数组为: + +```cpp {.line-numbers} +-2 5 7 +``` +则其最大子序列和为$12$,$mx=14$。 + +**- ④ 第四次取第二行:** +```cpp {.line-numbers} +1 -2 6 +``` +则其最大子序列和为$6$,$mx=14$。 + +**- ⑤ 第五次取第二三行:** +```cpp {.line-numbers} +1 -2 6 +2 1 -3 +``` + +对每一列进行压缩: +第一列:$1+2=3$ +第二列:$-2+1=-1$ +第三列:$6+(-3)=3$ + +所以压缩后的一维数组为: +```cpp {.line-numbers} +3 -1 3 +``` + +则其最大子序列和为$5$,$mx=14$。 + +**- ⑥ 第六次取第三行:** +```cpp {.line-numbers} +2 1 -3 +``` + +则其最大子序列和为$3$,$mx=14$。 + +最后求得这个矩阵最大的子矩阵和为$14$ + +也就是第一二行的三四列 +```cpp {.line-numbers} + 6 4 +-2 6 +``` **$Code$**