1564 字
4 分钟
Atcoder_Beginner_Contest_468(A~G题)
2026-07-26
浏览量 239 · 访客 41

壁纸链接
题目链接:https://atcoder.jp/contests/abc468/tasks

A - Maximal Value#

直接遍历序列中除首尾外的每个位置ii,判断是否同时满足Ai>Ai1A_i>A_{i-1}Ai>Ai+1A_i>A_{i+1}。满足条件就将答案加一。

int a[110];
void solved() {
int n; cin >> n;
int cnt = 0;
for(int i = 1; i <= n;i++) cin >> a[i];
for(int i = 2; i < n;i++){
if(a[i] > a[i - 1] && a[i] > a[i + 1])
cnt++;
}
cout << cnt << endl;
}

B - Corridor Watch#

我们先记录字符串中所有初始为GG的位置。对于每个位置ii,将区间[max(0,iD),min(M1,i+D)][\max(0,i-D),\min(M-1,i+D)]内的字符全部标记为GG,最后统计仍为..的位置数量即可。
这里需要先保存所有初始的GG,再进行区间标记,避免把标记过程中产生的新GG错误地当成警卫位置继续扩展。

void solved() {
int D, M; cin >> M >> D;
string s; cin >> s;
vector<int> pos;
for(int i = 0; i < (int)s.size();i++){
if(s[i] == 'G')
pos.push_back(i);
}
int ans = 0;
for(auto it1 : pos){
for(int i = max(0, it1 - D); i <= min((int)s.size() - 1, it1 + D);i++)
s[i] = 'G';
}
for(auto it : s)
if(it == '.') ans++;
cout << ans << endl;
}

C - Between P and Q#

由于N10N\leq 10,可以直接枚举全排列。next_permutationnext\_permutation会将当前排列修改为字典序严格大于它的下一个排列,所以从PP开始不断调用该函数,直到得到QQ,期间经过的排列数量就是严格位于P,QP,Q之间的排列数量。
如果一开始就有PQP\geq Q,不存在同时满足大于PP且小于QQ的排列,答案为00。否则每生成一个不等于QQ的新排列就将答案加一。

void solved() {
int N; cin >> N;
vector<int> P(N), Q(N);
for(auto &it : P) cin >> it;
for(auto &it : Q) cin >> it;
if(P >= Q) {cout << 0 << endl; return;}
int ans = 0;
vector<int> cur = P;
while(next_permutation(cur.begin(), cur.end())){
if(cur == Q) break;
ans++;
}
cout << ans << endl;
}

D - Pre-Palindrome#

一个字符串能够通过至多修改一个字符变成回文,当且仅当对称位置字符不同的组数不超过11。因此我们只需要统计每个子串中有多少组不匹配的对称字符。
定义dp[l]dp[l]表示上一种长度下,以ll为左端点的子串中不匹配的对称字符组数。给子串的左右两端各扩展一个字符后,新的不匹配组数为 ndp[l]=dp[l+1]+[SlSr]ndp[l]=dp[l+1]+[S_l\neq S_r]。因为只关心这个值是否不超过11,所以代码中将大于等于22的状态统一截断为22
奇数长度与偶数长度的中心不同,因此分别从长度33和长度22开始,每次将长度增加22。长度为11的子串一定满足条件,先将答案初始化为NN;其余子串在转移后状态不超过11时计入答案。
整个转移只依赖前一种长度,所以可以使用滚动数组将空间复杂度降为O(N)O(N),时间复杂度为O(N2)O(N^2)

void solved() {
string s; cin >> s;
int n = s.size();
ll ans = n;
for(int len : {2, 3}){
vector<int> dp(n + 1, 0), ndp(n + 1, 0);
for(int i = len; i <= n;i += 2){
int substr_cnt = n - i + 1;
for(int j = 0; j < substr_cnt;j++){
int r = j + i - 1;
ndp[j] = min(dp[j + 1] + (s[j] != s[r]), 2);
if(ndp[j] <= 1) ans++;
}
dp.swap(ndp);
}
}
cout << ans << endl;
}

E - Sum of Average#

直接枚举所有区间并计算平均值的复杂度至少为O(N2)O(N^2),所以这里考虑分别计算每个AiA_i对答案的贡献。对于一个包含位置ii的区间[l,r][l,r]AiA_i产生的系数为1rl+1\frac{1}{r-l+1}。因此答案可以写成 i=1NAiCi\sum_{i=1}^{N}A_iC_i,其中 Ci=lir1rl+1C_i=\sum_{l\leq i\leq r}\frac{1}{r-l+1}
记调和数Hk=j=1k1jH_k=\sum_{j=1}^{k}\frac{1}{j}。当i=1i=1时,包含A1A_1的区间只有[1,r][1,r],所以C1=HNC_1=H_N。从CiC_i移动到Ci+1C_{i+1}时,新增加的是左端点为i+1i+1的区间,其系数和为HNiH_{N-i};被删除的是右端点为ii的区间,其系数和为HiH_i。因此有 Ci+1=Ci+HNiHiC_{i+1}=C_i+H_{N-i}-H_i
我们预处理11NN在模998244353998244353意义下的逆元,再求出所有调和数,就可以在线性时间内递推每个位置的系数并累加贡献。时间复杂度为O(N)O(N),空间复杂度为O(N)O(N)

vector<ll> get_inverse(ll n, ll MOD){
vector<ll> inverse(n + 1);
inverse[1] = 1;
for(ll i = 2; i <= n;i++){
inverse[i] = MOD - (MOD / i) * (inverse[MOD % i]) % MOD;
}
return inverse;
}
void solved() {
int N; cin >> N;
vector<ll> inverse = get_inverse(N, mod);
vector<ll> harmonic(N + 1, 0);
for(int i = 1; i <= N;i++){
harmonic[i] = (harmonic[i - 1] + inverse[i]) % mod;
}
ll ans = 0, coeffic = harmonic[N];
for(int i = 1; i <= N;i++){
ll val; cin >> val;
ans = (ans + val * coeffic) % mod;
if(i < N){
coeffic += harmonic[N - i] - harmonic[i];
coeffic %= mod;
if(coeffic < 0) coeffic += mod;
}
}
cout << ans << endl;
}

F - Chmax#

每个元素都会被分配给x,yx,y中的一个变量,只有当它大于对应变量此前的最大值时才会产生一次贡献。等价地说,我们需要将原排列划分为两个子序列,最大化两个子序列中前缀最大值的数量之和。
先考虑原排列中的全局前缀最大值,即Pi>max(P1,,Pi1)P_i>\max(P_1,\ldots,P_{i-1})的位置。它出现时一定大于x,yx,y当前的值,所以无论分配给哪一个变量都会产生贡献。代码用premaxpremax维护当前全局最大值,并用cntcnt统计这部分必然能够取得的贡献。
对于不是全局前缀最大值的元素,x,yx,y中至少有一个已经保存了比它更大的全局最大值。若想让该元素产生贡献,只能把它放入另一个较小的变量中,而且后续在这个变量中产生贡献的非前缀最大值必须严格递增。因此这部分最多能选出非前缀最大值序列的一条最长上升子序列。反过来,将所有全局前缀最大值放入一个变量,将这条最长上升子序列放入另一个变量,就能取得这个上界。
所以最终答案等于全局前缀最大值的数量加上其余元素的LISLIS长度。代码使用tailstails数组和二分查找维护LISLIS,总时间复杂度为O(NlogN)O(NlogN),空间复杂度为O(N)O(N)

void solved() {
int N; cin >> N;
int premax = 0, cnt = 0;
vector<int> tails;
for(int i = 1; i <= N;i++){
int val; cin >> val;
if(premax < val){
premax = val;
cnt++;
}
else{
auto it = lower_bound(tails.begin(), tails.end(), val);
if(it == tails.end()){
tails.push_back(val);
}
else *it = val;
}
}
cout << cnt + (int)tails.size() << endl;
}

G - Restricted Permutation#

我们按照数值从小到大,将1,2,,N1,2,\ldots,N依次放入最终排列的各个位置。已经放置1k1\sim k后,它们在排列中的最左位置到最右位置构成一个最小覆盖区间。因为区间内共有spanspan个位置而已经放置了kk个数,所以是否连续只需要判断span=kspan=k
定义dp[span]dp[span]表示已经放置1k1\sim k,对于任意一个固定的长度为spanspan的最小覆盖区间,合法放置方案的数量。接下来放置k+1k+1,设新的覆盖区间长度为new_spannew\_span,转移分为两类:
第一类是把k+1k+1放在原覆盖区间内部的空位,此时覆盖区间长度不变,共有new_spanknew\_span-k个空位,贡献为(new_spank)×dp[new_span](new\_span-k)\times dp[new\_span]
第二类是把k+1k+1放在新覆盖区间的左端点或右端点。删除这个新端点后,原来的最小覆盖区间长度可以是任意小于new_spannew\_span的值,左右两种放法产生的贡献为2span<new_spandp[span]2\sum_{span<new\_span}dp[span]。这个前缀和可以在枚举new_spannew\_span时同步维护,使每次转移降为O(1)O(1)
完成转移后,1k+11\sim k+1连续当且仅当new_span=k+1new\_span=k+1。我们根据Sk+1S_{k+1}ooxx,只保留连续性与其相符的状态。特别地,集合{1}\{1\}以及完整集合{1,2,,N}\{1,2,\ldots,N\}在任何排列中都必然连续,所以S1S_1SNS_Nxx时答案直接为00
初始状态为dp[1]=1dp[1]=1,最终只有覆盖整个排列的状态dp[N]dp[N]需要计入答案。时间复杂度为O(N2)O(N^2),空间复杂度为O(N)O(N)

void solved() {
int N; cin >> N;
string s; cin >> s;
// {1} 和 {1,2,...,n} 在任何排列中都一定连续。
if(s[0] == 'x' || s[N - 1] == 'x'){
cout << 0 << endl;
return;
}
/*
dp[span]表示已经放置数字 1...placed,它们的最小覆盖区间长度为 span;
对于任意一个固定的、长度为 span 的区间,满足此前条件的放置顺序数量。
*/
vector<ll> dp(N + 1, 0), ndp(N + 1, 0);
dp[1] = 1;
for(int placed = 1; placed < N;placed++){
fill(ndp.begin(), ndp.end(), 0);
// prefix = dp[1] + dp[2] + ... + dp[new_span - 1];
ll prefix = 0;
for(int new_span = 1; new_span <= N;new_span++){
if(new_span >= placed + 1){
//在原覆盖区域内容有new_span - placed个空位
ll ways = (ll)(new_span - placed) * dp[new_span] % mod;
//新位置为新区间的左端点和右端点两种情况,所以乘2
ways = (ways + 2 * prefix) % mod;
//判断连续(不连续)是否满足'o'('x'),满足才更新
bool is_contiguous = (new_span == placed + 1);
bool required = (s[placed] == 'o');
if(is_contiguous == required)
ndp[new_span] = ways;
}
prefix += dp[new_span];
if(prefix >= mod) prefix -= mod;
}
dp.swap(ndp);
}
cout << dp[N] << endl;
}

模板:

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ull = unsigned long long;
#define endl '\n'
#define mod 998244353
typedef pair<ll, ll> pll;
typedef pair<int, int> pii;
ll fast_pow(ll a, ll b, ll MOD) {
ll ans = 1;
while (b) {
if (b & 1) ans = (ans * a) % MOD;
b >>= 1;
a = (a * a) % MOD;
}
return ans;
}
vector<ll> get_inverse(ll n, ll MOD){
vector<ll> inverse(n + 1);
inverse[1] = 1;
for(ll i = 2; i <= n;i++){
inverse[i] = MOD - (MOD / i) * (inverse[MOD % i]) % MOD;
}
return inverse;
}
void solved() {
}
int main() {
ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
int _ = 1;
//cin >> _;
while (_--) {
solved();
}
return 0;
}
分享

如果这篇文章对你有帮助,欢迎分享给更多人!

Atcoder_Beginner_Contest_468(A~G题)
https://mkrari.cn/posts/abc_468/
作者
Mkrari
发布于
2026-07-26
许可协议
CC BY-NC-SA 4.0