牛客刷题-Day9
牛客刷题-Day9
今日刷题:\(1041-1045\),好多状态表示都想不出来,😞。
1041 [NOIP2010]乌龟棋
题目描述
小明过生日的时候,爸爸送给他一副乌龟棋当作礼物。
乌龟棋的棋盘是一行 \(N\) 个格子,每个格子上一个分数(非负整数)。棋盘第 \(1\) 格是唯一的起点,第 \(N\) 格是终点,游戏要求玩家控制一个乌龟棋子从起点出发走到终点。
乌龟棋中 \(M\) 张爬行卡片,分成 \(4\) 种不同的类型(\(M\) 张卡片中不一定包含所有 \(4\) 种类型的卡片见样例),每种类型的卡片上分别标有 \(1\)、\(2\)、\(3\)、\(4\) 四个数字之一,表示使用这种卡片后,乌龟棋子将向前爬行相应的格子数。游戏中,玩家每次需要从所有的爬行卡片中选择一张之前没有使用过的爬行卡片,控制乌龟棋子前进相应的格子数,每张卡片只能使用一次。游戏中,乌龟棋子自动获得起点格子的分数,并且在后续的爬行中每到达一个格子,就得到该格子相应的分数。玩家最终游戏得分就是乌龟棋子从起点到终点过程中到过的所有格子的分数总和。
很明显,用不同的爬行卡片使用顺序会使得最终游戏的得分不同,小明想要找到一种卡片使用顺序使得最终游戏得分最多。
现在,告诉你棋盘上每个格子的分数和所有的爬行卡片,你能告诉小明,他最多能得到多少分吗?
输入描述
第 \(1\) 行 \(2\) 个正整数 \(N\) 和 \(M\),分别表示棋盘格子数和爬行卡片数。
第 \(2\) 行 \(N\) 个非负整数,\(a_1, a_2, ……, a_N\),其中 \(a_i\) 表示棋盘第 \(i\) 个格子上的分数。
第 \(3\) 行 \(M\) 个整数,\(b_1,b_2, ……, b_M\),表示 \(M\) 张爬行卡片上的数字。
输入数据保证到达终点时刚好用光 \(M\) 张爬行卡片,即 \(N−1=∑_1^Mb_i\)。
输出描述
输出只有 \(1\) 行,\(1\) 个整数,表示小明最多能得到的分数。
示例1
输入
9 5
6 10 14 2 8 8 18 5 17
1 3 1 2 1
输出
73
说明:小明使用爬行卡片顺序为 \(1\),\(1\),\(3\),\(1\),\(2\),得到的分数为 \(6+10+14+8+18+17=73\)。注意,由于起点是 \(1\),所以自动获得第 \(1\) 格的分数 \(6\)。
示例2
输入
13 8
4 96 10 64 55 13 94 53 5 24 89 8 30
1 1 1 1 1 2 4 1
输出
455
备注
对于 \(30\%\) 的数据有 \(1≤N≤30\),\(1≤M≤12\)。
对于 \(50\%\) 的数据有 \(1≤N≤120\),\(1≤M≤50\),且 \(4\) 种爬行卡片,每种卡片的张数不会超过 \(20\)。
对于 \(100\%\) 的数据有 \(1≤N≤350\),\(1≤M≤120\),且 \(4\) 种爬行卡片,每种卡片的张数不会超过 \(40\);
\(0≤a_i≤100\),\(1≤i≤N\);\(1≤b_i≤4\),\(1≤i≤M\)。输入数据保证 \(N−1=∑_1^Mb_i\)
解题思路
- 状态表示:\(f_{i,j,p,q}\) 表示四种卡片分别使用 \(i,j,p,q\) 张获得的分数,需要求解分数的最大值。
- 状态计算:在不使用卡片的情况下,在第一个格子,自动获得 \(a_1\),即初始状态 \(f_{0,0,0,0}=a_1\)。一般情况下,四种卡片分别使用 \(i,j,p,q\) 张,此时位于 \(k=i+2*j+3*p+4*q\) 处,则状态转移方程为 \(f_{i,j,p,q}=max\{f_{i,j,p,q}, max\{f_{i-1,j,p,q},f_{i,j-1,p,q},f_{i,j,p-1,q},f_{i,j,p,q-1}\}+a_k\}\)。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
const int N = 400, M = 50;
int n, m;
int a[N], f[M][M][M][M];
int card[5];
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]);
for (int i = 1; i <= m; i++) {
int b;
scanf("%d", &b);
card[b]++;
}
memset(f, 128, sizeof f);
f[0][0][0][0] = a[1];
for (int i = 0; i <= card[1]; i++)
for (int j = 0; j <= card[2]; j++)
for (int p = 0; p <= card[3]; p++)
for (int q = 0; q <= card[4]; q++) {
int k = i + 2 * j + p * 3 + q * 4 + 1;
if (i) f[i][j][p][q] = max(f[i][j][p][q], f[i - 1][j][p][q] + a[k]);
if (j) f[i][j][p][q] = max(f[i][j][p][q], f[i][j - 1][p][q] + a[k]);
if (p) f[i][j][p][q] = max(f[i][j][p][q], f[i][j][p - 1][q] + a[k]);
if (q) f[i][j][p][q] = max(f[i][j][p][q], f[i][j][p][q - 1] + a[k]);
}
printf("%d\n", f[card[1]][card[2]][card[3]][card[4]]);
return 0;
}
1042 To the Max
题目描述
Given a two-dimensional array of positive and negative integers, a sub-rectangle is any contiguous sub-array of size \(1*1\) or greater located within the whole array. The sum of a rectangle is the sum of all the elements in that rectangle. In this problem the sub-rectangle with the largest sum is referred to as the maximal sub-rectangle.
As an example, the maximal sub-rectangle of the array:
0 -2 -7 0
9 2 -6 2
-4 1 -4 1
-1 8 0 -2
is in the lower left corner:
9 2
-4 1
-1 8
and has a sum of \(15\).
输入描述
The input consists of an \(N*N\) array of integers. The input begins with a single positive integer \(N\) on a line by itself, indicating the size of the square two-dimensional array. This is followed by \(N^2\) integers separated by whitespace (spaces and newlines). These are the \(N^2\) integers of the array, presented in row-major order. That is, all numbers in the first row, left to right, then all numbers in the second row, left to right, etc. \(N\) may be as large as \(100\). The numbers in the array will be in the range \([-127,127]\).
输出描述
Output the sum of the maximal sub-rectangle.
示例
输入
4
0 -2 -7 0
9 2 -6 2
-4 1 -4 1
-1 8 0 -2
输出
15
解题思路
\(N\) may be as large as \(100\),数据范围为 \(100\)。
借助 P1115 最大子段和 的思路,可以固定子矩阵的行数,即枚举 \(1\le i\le j\le n\),这样就可以降维成一维的最大子段和。
最大子段和,状态 \(f_i\) 表示以 \(a_i\) 为结尾的连续子段和,则 \(f_i=max\{f_{i-1}+a_i,a_i\}\)。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int n, a[N][N];
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++) {
scanf("%d", &a[i][j]);
a[i][j] = a[i - 1][j] + a[i][j]; // 列的前缀和
}
int res = -1e9;
for (int i = 1; i <= n; i++)
for (int j = i; j <= n; j++) {
int sum = 0;
for (int k = 1; k <= n; k++) {
sum = max(sum, 0) + (a[j][k] - a[i - 1][k]);
res = max(res, sum);
}
}
printf("%d\n", res);
return 0;
}
1043 [ZJOI2007]棋盘制作
题目描述
国际象棋是世界上最古老的博弈游戏之一,和中国的围棋、象棋以及日本的将棋同享盛名。
据说国际象棋起源 于易经的思想,棋盘是一个 \(8\times 8\) 大小的黑白相间的方阵,对应八八六十四卦,黑白对应阴阳。
而我们的主人公小 \(Q\), 正是国际象棋的狂热爱好者。作为一个顶尖高手,他已不满足于普通的棋盘与规则,于是他跟他的好朋友小 \(W\) 决定将棋盘扩大以适应他们的新规则。
小 \(Q\) 找到了一张由 \(N\times M\) 个正方形的格子组成的矩形纸片,每个格子被涂有黑白两种颜色之一。小 \(Q\) 想在这种纸中裁减一部分作为新棋盘,当然,他希望这个棋盘尽可能的大。
不过小 \(Q\) 还没有决定是找 一个正方形的棋盘还是一个矩形的棋盘(当然,不管哪种,棋盘必须都黑白相间,即相邻的格子不同色),所以他希望可以找到最大的正方形棋盘面积和最大的矩形棋盘面积,从而决定哪个更好一些。于是小 \(Q\) 找到了即将参加全国信息学竞赛的你,你能帮助他么?
输入描述
第一行包含两个整数 \(N\) 和 \(M\),分别表示矩形纸片的长和宽。
接下来的 \(N\) 行包含一个 \(N\times M\) 的 \(01\) 矩阵,表示这张矩形纸片的颜色(\(0\) 表示白色,\(1\) 表示黑色)。
输出描述
包含两行,每行包含一个整数。
第一行为可以找到的最大正方形棋盘的面积,
第二行为可以找到的最大矩形棋盘的面积(注意正方形和矩形是可以相交或者包含的)。
示例
输入
3 3
1 0 1
0 1 0
1 0 0
输出
4
6
备注
对于 \(20\%\) 的数据,\(N, M ≤ 80\);
对于 \(40\%\) 的数据,\(N, M ≤ 400\);
对于 \(100\%\) 的数据,\(N, M ≤ 2000\)。
解题思路
需要确定子矩阵的范围,需要确定子矩阵的上下左右边界,这里使用 \(up、left、right\) 三个数组表示 \((i,j)\) 点可以扩展的上、左、右边界,下边界为 \(i\),则初始化时每个点的 \(up_{i,j}=1\),\(left_{i,j}=j\)、\(right_{i,j}=j\)。
\(left_{i,j}=max\{left_{i,j},left_{i-1,j}\},right_{i,j}=min\{right_{i,j},right_{i-1,j}\}\),这里表示从 \(i\) 向上 \(up_{i,j}\) 确定边界位置,可以参考木板理论,都是取短板。
悬线法很适合求解子矩阵的问题。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
const int N = 2010;
int n, m, a[N][N];
int l[N][N], r[N][N], u[N][N];
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) {
scanf("%d", &a[i][j]);
u[i][j] = 1;
l[i][j] = r[i][j] = j;
}
for (int i = 1; i <= n; i++)
for (int j = 2; j <= m; j++)
if (a[i][j] != a[i][j - 1])
l[i][j] = l[i][j - 1];
for (int i = 1; i <= n; i++)
for (int j = m; j > 1; j--)
if (a[i][j - 1] != a[i][j])
r[i][j - 1] = r[i][j];
int ans1 = 0, ans2 = 0;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) {
if (i > 1 && a[i][j] != a[i - 1][j]) {
u[i][j] = u[i - 1][j] + 1;
l[i][j] = max(l[i][j], l[i - 1][j]);
r[i][j] = min(r[i][j], r[i - 1][j]);
}
int b = r[i][j] - l[i][j] + 1;
int c = min(b, u[i][j]);
ans1 = max(ans1, c * c);
ans2 = max(ans2, b * u[i][j]);
}
printf("%d\n%d", ans1, ans2);
return 0;
}
1044 [SCOI2005]最大子矩阵
题目描述
这里有一个 \(n\times m\) 的矩阵,请你选出其中 \(k\) 个子矩阵,使得这个 \(k\) 个子矩阵分值之和最大。
注意:选出的 \(k\) 个子矩阵 不能相互重叠。
输入描述
第一行为 \(n,m,k\)(\(1 ≤ n ≤ 100,1 ≤ m ≤ 2,1 ≤ k ≤ 10\)),
接下来 \(n\) 行描述矩阵每行中的每个元素的分值(每个元素的分值的绝对值不超过 \(32767\))。
输出描述
只有一行为 \(k\) 个子矩阵分值之和最大为多少。
示例
输入
3 2 2
1 -3
2 3
-2 3
输出
9
解题思路
这里的 \(m\) 范围很特殊,可以分类讨论。如果 \(m=1\) 则,\(f_{i,k}\) 表示以第 \(i\) 行元素为结尾的选择的最后一个区间,共计选择 \(k\) 个区间的分值,则 \(f_{i,k}=max\{f_{i,k},f_{j,k-1}+sum_i-sum_j\}\)。
如果 \(m=2\),则 \(f_{i,j,k}\) 表示以第一列第 \(i\) 行元素为结尾的选择的最后一个区间,以第二列第 \(j\) 行元素为结尾的选择的最后一个区间,共计选择 \(k\) 个区间的分值。这里状态转移分三种情况,选择第一列第 \(i\) 行元素为结尾的区间,选择第二列第 \(j\) 行元素为结尾的区间、或者 \(i==j\) 时两个都选择。都选择时可以并为一个子矩阵。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
const int N = 110, M = 5, K = 15;
int n, m, k;
int a[N][M], sum[N][M];
int f[N][N][K]; // m = 2 时 f[i][j][k] 表示第一列前 i 第二列前 j 总共 k 个
int main() {
scanf("%d%d%d", &n, &m, &k);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) {
scanf("%d", &a[i][j]);
sum[i][j] = sum[i - 1][j] + a[i][j];
}
if (m == 1) {
for (int t = 1; t <= k; t++)
for (int i = 1; i <= n; i++) {
f[i][0][t] = f[i - 1][0][t];
for (int j = 0; j < i; j++)
f[i][0][t] = max(f[i][0][t], f[j][0][t - 1] + sum[i][m] - sum[j][m]);
}
printf("%d\n", f[n][0][k]);
return 0;
}
for (int t = 1; t <= k; t++)
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++) {
f[i][j][t] = max(f[i - 1][j][t], f[i][j - 1][t]);
for (int p = 0; p < i; p++)
f[i][j][t] = max(f[i][j][t], f[p][j][t - 1] + sum[i][1] - sum[p][1]);
for (int p = 0; p < j; p++)
f[i][j][t] = max(f[i][j][t], f[i][p][t - 1] + sum[j][2] - sum[p][2]);
if (i == j)
for (int p = 0; p < j; p++)
f[i][j][t] = max(f[i][j][t], f[p][p][t - 1] + sum[i][1] - sum[p][1] + sum[j][2] - sum[p][2]);
}
printf("%d\n", f[n][n][k]);
return 0;
}
本文来自博客园,作者:Cocoicobird,转载请注明原文链接:https://www.cnblogs.com/Cocoicobird/p/19129658