PRE 杯(#2026.1.14 Lv.2)题解
A
by DeepSeek-V3
题目分析
给定一个正整数 \(S\)(\(1 \le S \le 10^9\)),将其十进制表示的数字串连续不相交地分割成若干段,每段不能有前导零(但允许单字符的 "0"),记这些段对应的数值为 \(b_1, b_2, \dots, b_k\),要求:
[
]
如果存在这样的分割,任意输出一种,先输出段数 \(k\),再输出 \(b_1, b_2, \dots, b_k\);否则输出 -1。
解题思路
由于 \(S \le 10^9\),其十进制位数 \(L\) 最多为 \(10\)。
分割点只能位于相邻数字之间,因此总共有 \(L-1\) 个可能的分割位置,每个位置可以选择“切”或“不切”。
总的分割方案数为 \(2^{L-1}\),当 \(L=10\) 时最多 \(2^9 = 512\) 种,非常少,可以直接枚举。
步骤
- 将 \(S\) 读入为字符串
s,长度n。 - 用掩码
mask从 \(0\) 到 \(2^{n-1}-1\) 枚举所有分割方案。- 若
mask的第 \(i\) 位(\(0 \le i \le n-2\))为1,表示在s[i]和s[i+1]之间切一刀。
- 若
- 根据
mask分割出各段:- 记录上一段起点
last,初始为0。 - 遍历
i从0到n-2,若第i位为1,则当前段为s[last..i]。 - 检查该段是否合法:
- 段长度 \(\ge 2\) 且第一个字符为
'0'→ 不合法。 - 如果是第一段且数值为
0→ 不合法(题目要求 \(b_1 > 0\))。
- 段长度 \(\ge 2\) 且第一个字符为
- 将段转换为数值并累加。
- 更新
last = i+1。
- 记录上一段起点
- 最后一段为
s[last..n-1],同样检查合法性。 - 计算所有段的和
sum,若sum * sum == S,则找到答案,输出段数和各段数值。 - 若所有掩码都尝试完仍未找到,输出
-1。
时间复杂度
枚举 \(O(2^{L-1})\),每次分割 \(O(L)\),\(L \le 10\),实际运行极快。
注意事项
- 使用
long long存储sum和sum*sum,防止整数溢出。 - 段数值为
0是允许的,但只能作为非首段,且必须是单独的字符"0"(即长度为1)。 - 注意
S=1的情况:只能分成一段"1",和1,平方1,输出1\n1。
代码实现
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int main() {
string s;
cin >> s;
int n = s.size();
long long S = stoll(s);
int totalMasks = (n == 1) ? 1 : (1 << (n - 1));
for (int mask = 0; mask < totalMasks; ++mask) {
vector<long long> parts;
int last = 0;
bool valid = true;
long long sum = 0;
// 根据掩码分割
for (int i = 0; i < n - 1; ++i) {
if (mask & (1 << i)) {
int len = i - last + 1;
// 检查前导零(长度>1且首字符为'0')
if (len > 1 && s[last] == '0') {
valid = false;
break;
}
string sub = s.substr(last, len);
long long val = stoll(sub);
// 第一段不能为0
if (parts.empty() && val == 0) {
valid = false;
break;
}
parts.push_back(val);
sum += val;
last = i + 1;
}
}
if (!valid) continue;
// 最后一段
int len = n - last;
if (len > 1 && s[last] == '0') continue;
string sub = s.substr(last, len);
long long val = stoll(sub);
if (parts.empty() && val == 0) continue;
parts.push_back(val);
sum += val;
// 检查条件
if (sum * sum == S) {
cout << parts.size() << "\n";
for (size_t i = 0; i < parts.size(); ++i) {
if (i) cout << " ";
cout << parts[i];
}
cout << "\n";
return 0;
}
}
cout << "-1\n";
return 0;
}
样例验证
样例1
输入:2025
输出:
2
20 25
解释:分为 20 和 25,和 45,\(45^2=2025\)。
样例2
输入:13
输出:-1
无解。
样例3
输入:10000
输出:
3
100 0 0
解释:分为 100、0、0,和 100,\(100^2=10000\)。
总结
本题的核心在于发现数据范围极小(\(L\le 10\)),因此可以使用二进制枚举所有分割方式。
注意细节:前导零规则、首段非零、以及 \(0\) 作为单独段的合法性。
按题意模拟即可通过。
A - code - checker
#include <bits/stdc++.h>
#define ll long long
using namespace std;
bool flag;
int n,p10[50];
int a[100];
void dfs (int k){
if (flag) return ;
if (a[k - 1] == 0){
int sum = 0;
sum += n / p10[a[1]];
for (int i = 2; i < k; i++)
sum += (n % p10[a[i - 1]]) / p10[a[i]];
if (sum * sum == n){
cout << k - 1 << '\n';
cout << n / p10[a[1]] << ' ';
for (int i = 2; i < k; i++) cout << (n % p10[a[i - 1]]) / p10[a[i]] << ' ';
flag = 1;
}
return ;
}
for (int i = a[k - 1] - 1; i >= 0; i--){
a[k] = i;
dfs (k + 1);
}
}
int main(){
p10[0] = 1;
for (int i = 1; i <= 9; i++) p10[i] = p10[i - 1] * 10;
cin >> n;
if (n == 1){
cout << 1 << '\n' << 1;
return 0;
}
a[0] = log10(n) + 1;
dfs (1);
if (!flag){
cout << -1;
}
}
B
by DeepSeek-V3
题目分析
给定一个 \(n \times m\) 的整数矩阵,以及一个整数 \(k\)。对于每个内部单元格(即不在最外圈,\(2 \le i \le n-1,\ 2 \le j \le m-1\)),其权值定义为以它为中心的 \(3 \times 3\) 子矩阵中的最大值。初始时,所有内部单元格的权值之和为 \(S_0\)。
允许将任意一个单元格(可以是边界)的值修改为 \(k\)。修改后,只有那些 \(3 \times 3\) 邻域包含被修改单元格的内部单元格的权值可能发生变化。需要找到一种修改位置,使得修改后的权值和最大,输出该位置和对应的最大权值和。
算法思路
暴力枚举每个修改位置并重新计算所有受影响的内部单元格的权值是 \(O(9nm)\) 的,而 \(n,m \le 5000\),\(nm = 2.5 \times 10^7\),\(9nm \approx 2.25 \times 10^8\),在优化良好的 C++ 中勉强可过。但为了内存安全和进一步优化,我们采用分块预处理的方法,将内存占用从 \(O(nm)\) 降低到 \(O(\text{块大小} \times m)\)。
核心思想
-
预处理每个内部单元格的 \(3 \times 3\) 区域信息:最大值、次大值、最大值的出现次数。
因为修改一个单元格只会影响其周围 \(9\) 个区域(如果存在),所以只需知道每个区域的最大值和次大值,就能快速计算修改后的新权值。 -
分块处理修改位置:
将矩阵的行分成若干块,每块高度为 \(B\)(例如 \(B=100\))。对于当前块(行范围 \([rs, re]\)),我们只关心修改位置在该块内的单元格。为了计算这些单元格的权值和变化,需要知道所有可能覆盖它们的区域信息,这些区域的行范围是 \([ \max(2, rs-1),\ \min(n-1, re+1) ]\),宽度为 \(B+2\) 左右。
因此,我们可以临时计算出这些区域的信息(数量约 \((B+2)\times(m-2)\),内存约 \(6\text{MB}\)),然后对于块内的每个修改位置,遍历其周围的 \(9\) 个区域(实际可能不足 \(9\)),利用区域信息计算新权值,累加变化量,并与全局最优比较。 -
初始权值和 \(S_0\) 可以在预处理区域信息时一并求得。
算法步骤
- 读入 \(n,m,k\) 和矩阵 \(a\)(使用 \(0\)‑索引,存储为一维数组
grid)。 - 初始化全局最优值
best = -inf,最优位置(bestX, bestY)。 - 按行分块,设块高 \(B = 100\)(也可根据内存调整)。
对每个块(起始行 \(rs\),结束行 \(re = \min(n-1, rs+B-1)\)):- 确定需要的区域行范围:
iL = max(2, rs-1),iR = min(n-1, re+1)。
区域列范围为 \([2, m-1]\)(\(1\)‑索引,对应 \(0\)‑索引的 \([1, m-2]\))。 - 计算这些区域的信息:
创建三个一维数组maxv,secv,cntv,大小为R * C,其中R = iR - iL + 1,C = m-2。
对于每个区域中心(i, j)(\(i \in [iL, iR]\), \(j \in [2, m-1]\)),扫描其 \(3 \times 3\) 邻域,统计最大值、次大值及最大值出现次数,存入对应的数组位置。 - 同时累加初始权值和
sum0(只需累加一次,可在第一个块中完成,但为简单可在每个块都计算,取第一次结果)。 - 遍历修改位置:对于每个
x属于当前块的行(\(x \in [rs, re]\)),每个y属于 \([0, m-1]\)(\(0\)‑索引):- 计算变化量
delta = 0。 - 遍历 \(i \in [\max(2, x-1),\ \min(n-1, x+1)]\),\(j \in [\max(2, y-1),\ \min(m-1, y+1)]\):
- 找到对应区域在数组中的索引
r = i - iL,c = j - 2。 - 获取
M = maxv[r*C + c],S = secv[r*C + c],cnt = cntv[r*C + c]。 - 计算该区域的新权值
newVal:- 若 \(k > M\):
newVal = k - 若 \(k == M\):
newVal = M - 若 \(k < M\):
若cnt > 1:newVal = M
否则:newVal = max(k, S)
- 若 \(k > M\):
delta += newVal - M
- 找到对应区域在数组中的索引
- 总权值
total = sum0 + delta。 - 如果
total > best,更新best = total和bestX = x+1, bestY = y+1(输出要求 \(1\)‑索引)。
- 计算变化量
- 确定需要的区域行范围:
- 输出
bestX, bestY和best。
复杂度分析
- 时间:每个块需要计算区域信息 \(O((B+2) \cdot m \cdot 9)\),遍历修改位置 \(O(B \cdot m \cdot 9)\),总块数约 \(n/B\),因此总时间复杂度为 \(O(9nm)\),即约 \(2.25 \times 10^8\) 次简单运算,在 C++ 中优化后通常可接受。
- 空间:主要存储整个矩阵 \(O(nm)\) 个
int,约 \(100\text{MB}\);临时存储区域信息 \(O(B \cdot m)\) 个int,约 \(6\text{MB}\);其他少量变量。总内存控制在 \(110\text{MB}\) 左右,完全满足常见内存限制(如 \(256\text{MB}\))。
实现细节与优化
- 使用一维数组存储矩阵,按行优先:
grid[i*m + j]。 - 使用
scanf/printf进行快速输入输出。 - 由于 \(k\) 和矩阵值可能很大,涉及比较和加法时使用
long long避免溢出。 - 注意边界处理:矩阵行列从 \(0\) 开始索引,但内部单元格的行列范围是 \([1, n-2]\)(\(0\)‑索引),输出时加 \(1\)。
- 块大小 \(B\) 可调节,\(100\) 是一个经验值,使临时数组大小约为 \(100 \times 5000 \times 3 \times 4 \approx 6\text{MB}\),同时保证分块数量适中。
代码实现
#include <cstdio>
#include <algorithm>
#include <climits>
using namespace std;
const int MAXN = 5005;
int n, m, k;
int *grid; // 一维数组存储矩阵
long long sum0; // 初始权值和
// 计算某个3x3区域的最大值、次大值和最大值出现次数
void get_region_info(int i, int j, int &mx, int &sec, int &cnt) {
mx = sec = INT_MIN;
cnt = 0;
for (int di = -1; di <= 1; ++di) {
for (int dj = -1; dj <= 1; ++dj) {
int val = grid[(i+di)*m + (j+dj)];
if (val > mx) {
sec = mx;
mx = val;
cnt = 1;
} else if (val == mx) {
++cnt;
} else if (val > sec) {
sec = val;
}
}
}
}
int main() {
scanf("%d%d%d", &n, &m, &k);
grid = new int[n*m];
for (int i = 0; i < n; ++i)
for (int j = 0; j < m; ++j)
scanf("%d", &grid[i*m + j]);
const int B = 100; // 块高
long long best = -1e18;
int bestX = -1, bestY = -1;
for (int rs = 0; rs < n; rs += B) {
int re = min(n-1, rs + B - 1);
// 需要的区域行范围
int iL = max(1, rs - 1);
int iR = min(n-2, re + 1);
int R = iR - iL + 1;
int C = m - 2; // 内部单元格列数
if (R <= 0 || C <= 0) continue;
// 分配临时数组存储区域信息
int *maxv = new int[R*C];
int *secv = new int[R*C];
int *cntv = new int[R*C];
// 计算每个区域的信息,同时累加初始权值和(只在第一次块中累加)
bool firstBlock = (rs == 0);
if (firstBlock) sum0 = 0;
for (int i = iL; i <= iR; ++i) {
for (int j = 1; j <= m-2; ++j) { // j是内部单元格的列索引(0-index)
int idx = (i - iL)*C + (j - 1);
int mx, sec, cnt;
get_region_info(i, j, mx, sec, cnt);
maxv[idx] = mx;
secv[idx] = sec;
cntv[idx] = cnt;
if (firstBlock) sum0 += mx;
}
}
// 遍历当前块内的所有修改位置
for (int x = rs; x <= re; ++x) {
for (int y = 0; y < m; ++y) {
long long delta = 0;
// 遍历受影响的区域
int i_start = max(1, x-1);
int i_end = min(n-2, x+1);
int j_start = max(1, y-1);
int j_end = min(m-2, y+1);
for (int i = i_start; i <= i_end; ++i) {
for (int j = j_start; j <= j_end; ++j) {
int idx = (i - iL)*C + (j - 1);
int M = maxv[idx];
int S = secv[idx];
int cnt = cntv[idx];
long long newVal;
if (k > M) newVal = k;
else if (k == M) newVal = M;
else { // k < M
if (cnt > 1) newVal = M;
else newVal = max((long long)k, (long long)S);
}
delta += newVal - M;
}
}
long long total = sum0 + delta;
if (total > best) {
best = total;
bestX = x + 1; // 转为1-index
bestY = y + 1;
}
}
}
delete[] maxv;
delete[] secv;
delete[] cntv;
}
printf("%d %d\n%lld\n", bestX, bestY, best);
delete[] grid;
return 0;
}
注意事项
- 当 \(n\) 或 \(m\) 小于 \(3\) 时,没有内部单元格,权值和始终为 \(0\),任何修改都不会改变(但题目保证 \(3 \le n,m\),故不需特殊处理)。
- 次大值的初始化:如果区域中所有数都相等,则次大值应设为
INT_MIN,在 \(k < M\) 且cnt==1时,max(k, S)会正确得到 \(k\)(因为 \(S\) 很小)。如果 \(k\) 仍然小于 \(S\),则新最大值为 \(S\)。 - 由于 \(k\) 可能非常大(\(10^9\)),使用
long long进行比较。
总结
本题的关键在于理解修改对权值和的影响范围有限,以及利用分块技巧在保证时间复杂度的前提下大幅降低内存占用。通过预处理每个 \(3 \times 3\) 区域的最大值和次大值,可以 \(O(1)\) 计算每个区域修改后的权值,最终 \(O(9nm)\) 枚举所有修改位置即可得到最优解。
B - code - checker
#include <iostream>
#include <vector>
#include <algorithm>
#include <climits>
using namespace std;
typedef long long ll;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr); cout.tie(0);
int n, m;
ll k;
cin >> n >> m >> k;
vector<vector<ll>> a(n + 1, vector<ll>(m + 1));
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cin >> a[i][j];
}
}
// 中心范围: i in [2, n-1], j in [2, m-1]
vector<vector<ll>> first(n + 1, vector<ll>(m + 1, LLONG_MIN));
vector<vector<ll>> second(n + 1, vector<ll>(m + 1, LLONG_MIN));
vector<vector<int>> cnt_first(n + 1, vector<int>(m + 1, 0));
vector<vector<pair<int,int>>> pos_first(n + 1, vector<pair<int,int>>(m + 1, {-1, -1}));
// 预处理每个中心
for (int i = 2; i <= n - 1; i++) {
for (int j = 2; j <= m - 1; j++) {
ll mx1 = LLONG_MIN, mx2 = LLONG_MIN;
int cnt = 0;
pair<int,int> pos_mx = {-1, -1};
for (int dx = -1; dx <= 1; dx++) {
for (int dy = -1; dy <= 1; dy++) {
ll val = a[i + dx][j + dy];
if (val > mx1) {
mx2 = mx1;
mx1 = val;
cnt = 1;
pos_mx = {i + dx, j + dy};
} else if (val == mx1) {
cnt++;
} else if (val > mx2) {
mx2 = val;
}
}
}
first[i][j] = mx1;
if (mx2 == LLONG_MIN) {
second[i][j] = mx1; // 如果所有值相同,次大等于最大
} else {
second[i][j] = mx2;
}
cnt_first[i][j] = cnt;
if (cnt == 1) {
pos_first[i][j] = pos_mx;
} else {
pos_first[i][j] = {-1, -1};
}
}
}
// 原始总权值和
ll total = 0;
for (int i = 2; i <= n - 1; i++) {
for (int j = 2; j <= m - 1; j++) {
total += first[i][j];
}
}
ll best_sum = total;
int best_r = 1, best_c = 1;
// 遍历所有可能的修改位置
for (int r = 1; r <= n; r++) {
for (int c = 1; c <= m; c++) {
// 受影响中心范围
int i_low = max(2, r - 1);
int i_high = min(n - 1, r + 1);
int j_low = max(2, c - 1);
int j_high = min(m - 1, c + 1);
if (i_low > i_high || j_low > j_high) {
// 不影响任何中心
if (total > best_sum) {
best_sum = total;
best_r = r;
best_c = c;
}
continue;
}
ll delta_sum = 0;
for (int i = i_low; i <= i_high; i++) {
for (int j = j_low; j <= j_high; j++) {
ll old_first = first[i][j];
ll new_first;
if (cnt_first[i][j] > 1 || pos_first[i][j] != make_pair(r, c)) {
// (r,c) 不是唯一的最大值格子
new_first = max(old_first, k);
} else {
// (r,c) 是唯一的最大值格子
new_first = max(second[i][j], k);
}
delta_sum += new_first - old_first;
}
}
ll new_total = total + delta_sum;
if (new_total > best_sum) {
best_sum = new_total;
best_r = r;
best_c = c;
}
}
}
cout << best_r << " " << best_c << "\n";
cout << best_sum << "\n";
return 0;
}
C
初学者难度,dp 即可。不难想到的 dp 定义:\(dp_{i,j}\) 表示 \(b_i\) 为 \(j\) 的最小修改次数,路径记录也不难。
C - code
#include <bits/stdc++.h>
#define ll long long
const int N = 5e5 + 5,inf = 2e9;
using namespace std;
int n,f[N][2],p[N][2];
char a[N],b[N];
int main(){
cin >> n >> a + 1 >> b + 1;
memset (f, 0x3f, sizeof f);
f[0][0] = 0; f[0][1] = inf;
for (int i = 1; i <= n; i++){
for (int x = 0; x < 2; x++){
for (int y = 0; y < 2; y++){
int sum = 0;
if (x + '0' != a[i] && a[i] != '?') sum++;
if (y + '0' != b[i] && b[i] != '?') sum++;
if (f[i - 1][x ^ y] + sum < f[i][y]){
f[i][y] = f[i - 1][x ^ y] + sum;
p[i][y] = x ^ y;
}
}
}
}
cout << min (f[n][0], f[n][1]) << '\n';
int k = 1;
if (f[n][0] < f[n][1]) k = 0;
char aim_a[N],aim_b[N];
for (int i = n; i >= 1; i--){
aim_b[i] = k + '0';
k = p[i][k];
}
aim_a[1] = aim_b[1];
for (int i = 2; i <= n; i++){
aim_a[i] = (aim_b[i - 1] - '0') ^ (aim_b[i] - '0') + '0';
}
for (int i = 1; i <= n; i++) cout << aim_a[i]; cout << '\n';
for (int i = 1; i <= n; i++) cout << aim_b[i];
return 0;
}
D
题面链接
暂时没有写 qaq
E
题面链接
暂时没有写 qaq

浙公网安备 33010602011771号