这是C,B,G题的个人想法
C
要让数组总和最大,核心原则是:能替换成的数越大,总和就越大。结合题目操作规则,最终最优解是: 首尾元素无法被修改,保留原值;中间所有元素都替换成整个数组的全局最大值(这是能填充的最大数值),此时总和最大。
1. 分析操作规则的本质约束
题目允许任意次操作:选区间 (l, r),将区间内元素替换为 max(a[l], a[r])。 约束 1:首尾元素永远无法被修改(因为操作只能改 “区间内” 的元素,首尾只能当区间端点,无法被其他区间包含); 约束 2:要让总和最大,中间元素应尽可能替换成 “能达到的最大值”—— 而整个数组的全局最大值是所有元素中最大的,且一定能通过操作覆盖所有中间元素(比如以最大值所在位置为端点,分两次操作覆盖所有中间位置)。
2. 确定计算逻辑
基于约束,总和的计算公式为: 最终总和 = 第一个元素值 + 最后一个元素值 + 全局最大值 × (数组长度 - 2) 数组长度 - 2:中间可替换的元素数量(总长度减去首尾 2 个不可修改的元素); 全局最大值 × 中间元素数量:中间所有元素都替换成全局最大值的总和; 加上首尾元素:这两个元素无法修改,只能保留原值。
代码展示:
using namespace std;
int main() {
int T;
cin>>T;
while(T--) {
int n;
cin>>n;
long long a1, a2, x;
long long global_max = 0;
cin>>a1;
global_max = a1;
for(int i=1;i<=n-2;i++){
cin>>x;
if(x > global_max) {
global_max = x;
}
}
if(n>=2){
cin>>a2;
if(a2 > global_max) {
global_max = a2;
}
}
if(n<=2){
long long m;
if(n==1){
m=a1;
}
else{
m=a1+a2;
}
cout<<m<<endl;
continue;
}
cout<<a1 + a2 + global_max*(n-2)<<endl;
}
return 0;
}
B
核心思路:
先通过降序排列找到最大得分(等于a中大于b最小值的数字个数),再通过将a分为 “大于b_min” 和 “小于b_min” 两段,得出方案数是两段阶乘的乘积
(1)最大得分的推导:为什么是 “a 中大于 b 最小值的数字个数”?
- 你的核心观察非常关键,我们用通俗的语言拆解这个结论:
- 前提:题目规则是 “小苯和小红各出一张牌,小苯牌大则得分 + 1,然后两张牌都删除;重复此过程直到牌用完”。
- 最优策略(降序排 a):把a降序排列后,小苯每次都用当前最大的牌去匹配小红的牌 —— 而小红的牌里最小的那个b_min是最容易被赢的。
- 关键结论:
- 所有a中大于b_min的数,无论匹配小红的哪张牌,最终一定会 “赢一次”(因为哪怕先匹配小红的大牌,输了之后这张a的数会去匹配更小的b,最终总能赢b_min);
- 所有a中小于b_min的数,永远赢不了任何一张b的牌(因为b_min是小红最小的牌)。
- 因此,最大得分 = a中大于b_min的数字个数(记为x),剩余y = n - x个数永远赢不了。
(2)方案数的推导:为什么是x! * y!?
- 你的 “两段划分” 逻辑是这个结论的核心,我们用乘法原理和场景验证拆解:
- 必要条件:要拿到最大得分x,必须保证 “所有大于b_min的a的数,都能在b_min被匹配前用完 / 匹配到”—— 而实现这个目标的唯一要求是:把所有大于b_min的数放在a的前x个位置,小于的放在后y个位置。
- 如果不这么做(比如把一个小于b_min的数放在前x位),这个数会先匹配小红的牌,赢不了,且会 “占用一次匹配机会”,导致后面一个大于b_min的数无法匹配到b_min,得分减少。
- 乘法原理应用:
- 前x个位置(大于b_min的数):内部可以任意排列,有x!种方式;
- 后y个位置(小于b_min的数):内部可以任意排列,有y!种方式;
- 总方案数 = 前一段排列数 × 后一段排列数 = x! * y!(全程取模998244353)。
代码展示:
using namespace std;
typedef long long ll;
const int MOD = 998244353;
const int MAXN = 2e5 + 10;
ll fact[MAXN];
void preprocess() {
fact[0] = 1;
for (int i = 1; i < MAXN; i++) {
fact[i] = fact[i-1] * i % MOD;
}
}
int main() {
preprocess();
int T;
cin >> T;
while (T--) {
int n;
cin >> n;
vector<int> a(n), b(n);
int min_b = INT_MAX;
for (int i = 0; i < n; i++) cin >> a[i];
for (int i = 0; i < n; i++) {
cin >> b[i];
min_b = min(min_b, b[i]);
}
int x = 0;
for (int num : a) {
if (num > min_b) x++;
}
int y = n - x;
ll ans = fact[x] * fact[y] % MOD;
cout << ans << endl;
}
return 0;
}
G
核心思路:
放弃暴力遍历区间内所有数,转而精准枚举「折叠数可能最大」的关键候选数,通过计算这些候选数的折叠数来找到最大值。
1.核心观察:
折叠数的大小由反转后的数字高位决定,而末尾全是 9 的数(如 9、19、99、129、999 等)反转后高位是 9,折叠数大概率最大,因此这类数是最优解的核心候选。
2.枚举策略:
围绕 “末尾全 9 的数” 分层构造候选集,而非遍历全部数: 优先找 R 附近个位为 9 的数(比如 R=123 则找 119); 再找与 R 位数匹配的全 9 数(比如 R=1234 则找 999); 最后遍历所有位数的 “末尾全 9 数”(9、99、999…),覆盖所有可能; 兜底检查区间端点 L/R,避免极端情况漏检。
代码展示:
using namespace std;
long long reverse_num(long long a) {
long long res = 0;
while (a > 0) {
int dig = a % 10;
res = res * 10 + dig;
a /= 10;
}
return res;
}
int main() {
int t;
cin >> t;
while (t--) {
long long l, r;
cin >> l >> r;
long long max_fold = 0;
long long start = max(l, r - 1000);
for (long long x = start; x <= r; x++) {
max_fold = max(max_fold, reverse_num(x));
}
long long tenk = 1;
while (tenk <= r * 10) {
long long candidate = (r / tenk) * tenk + (tenk - 1);
if (candidate > r) {
if (r / tenk == 0) break;
candidate = (r / tenk - 1) * tenk + (tenk - 1);
}
if (candidate >= l && candidate <= r) {
max_fold = max(max_fold, reverse_num(candidate));
}
tenk *= 10;
}
cout << max_fold << endl;
}
return 0;
}

京公网安备 11010502036488号