diff --git a/TangDou/Topic/PrefixAndSuffix/P1719_2.cpp b/TangDou/Topic/PrefixAndSuffix/P1719_2.cpp index 4e82733..436da4e 100644 --- a/TangDou/Topic/PrefixAndSuffix/P1719_2.cpp +++ b/TangDou/Topic/PrefixAndSuffix/P1719_2.cpp @@ -1,33 +1,28 @@ #include 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; } \ No newline at end of file diff --git a/TangDou/Topic/【前缀和与差分】题单.md b/TangDou/Topic/【前缀和与差分】题单.md index fd9a525..a112aeb 100644 --- a/TangDou/Topic/【前缀和与差分】题单.md +++ b/TangDou/Topic/【前缀和与差分】题单.md @@ -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 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)