AtCoder Beginner Contest 468 ABCDE

A - Maximal Value

  • 预估难度:入门
  • 标签:一维数组、模拟

题意

给定一个长度为 \(N\) 的整数序列:\(A=(A_1,A_2,\ldots,A_N)\)

请找出有多少个整数 \(i\) 满足 \(1 \leq i \leq N-2\)\(A_i \lt A_{i+1} \gt A_{i+2}\)

数据范围

  • \(3\le N\le 100\)
  • \(1\le A_i\le 100\)

代码

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

B - Corridor Watch

  • 预估难度:入门
  • 标签:计数思想

题意

给定整数 \(M\)\(D\),以及一个长度为 \(M\) 的字符串 \(S\),字符串仅由字符 G. 组成。

\(M\) 个格子排成一行,从左到右依次编号为 \(1\)\(M\)

部分格子上站有守卫。具体来说,如果 \(S_i=\) G,则第 \(i\) 个格子上站有一名守卫;如果 \(S_i=\) .,则第 \(i\) 个格子上没有守卫。

如果某个格子与有守卫的格子之间的距离不超过 \(D\),则该格子会被该守卫监视。也就是说,如果存在一个格子 \(i\) 满足 \(S_i=\) G\(|x-i|\le D\),则格子 \(x\) 会被守卫监视。

在这 \(M\) 个格子中,请找出未被监视的格子数量。

数据范围

  • \(0\le D < M \le 100\)
  • \(S_i\) 是一个长度为 \(M\) 且仅由字符 G. 组成的字符串

思路

借助计数数组 vis[i] 用来标记格子 \(i\) 是否被某个守卫者监视。

如果 $S_i = $ G,说明存在一名守卫者能够管辖区间 \([i-D, i+D]\) 范围内的所有格子。

由于格子编号仅从 \(1\)\(M\) 过,因此实际管辖的区间左端点为 \(\max(i-D, 1)\),右端点为 \(\min(i+D, M)\)

将这段区间内的所有格子标记为被监视,最后统计有多少个格子没有被监视即可。

代码

bool vis[105];
// vis[i] 表示 i 这个格子是否被某个守卫者监视

void solve()
{
    int n, d;
    cin >> n >> d;
    for(int i = 1; i <= n; i++)
    {
        char c;
        cin >> c;
        if(c == 'G')
        {
            int l = max(1, i - d);
            int r = min(n, i + d);
            for(int j = l; j <= r; j++)
                vis[j] = true; // 标记这一段区间内的所有位置都会被监视
        }
    }
    
    int cnt = 0;
    for(int i = 1; i <= n; i++)
        if(!vis[i]) // 数没有被监视的格子数量
            cnt++;
    
    cout << cnt;
}

C - Between P and Q

  • 预估难度:普及-
  • 标签:STL函数 / 深度优先搜索

题意

给定一个整数 \(N\),以及两个整数序列 \(P=(P_1,P_2,\ldots, P_N)\)\(Q=(Q_1,Q_2,\ldots,Q_N)\),它们均为 \((1,2,\ldots,N)\) 的排列。

请求出有多少个整数序列,满足它是 \((1,2,\ldots,N)\) 的排列,且在字典序上严格大于 \(P\) 且严格小于 \(Q\)

数据范围

  • \(1\le N\le 10\)

思路

注意到 \(N \le 10\),并且 \(N\) 的全排列数量共 \(N!\) 种,因此可以考虑找出所有 \(N\) 的全排列再一一比较。

找全排列的方式有很多,一般做法为深度优先搜索或是借助 STL 函数 next_permutation 进行。

若是借助 next_permutation,一种更简便的方法是,我们可以直接从给定的序列 \(P\) 开始向后不断构造下一个字典序的排列,此时得到的序列一定严格大于原本的 \(P\) 序列,只需要判断是否严格小于 \(Q\) 序列即可。

时间复杂度 \(O(N\cdot N!)\)

代码

int n, p[15], q[15];

// 判断目前 p 序列字典序是否小于 q 序列
bool check()
{
    for(int i = 1; i <= n; i++)
        if(p[i] != q[i])
            return p[i] < q[i]; // 直接判断对应数字关系
    return false; // 说明两序列相同
}

void solve()
{
    cin >> n;
    for(int i = 1; i <= n; i++)
        cin >> p[i];
    for(int i = 1; i <= n; i++)
        cin >> q[i];
    
    int cnt = 0;
    // 先向后构造下一个字典序的排列,再判断 p 序列字典序是否小于 q 序列
    while(next_permutation(p + 1, p + n + 1) && check())
        cnt++;
    cout << cnt;
}

D - Pre-Palindrome

  • 预估难度:普及
  • 标签:枚举

题意

如果一个仅由小写英文字母组成的字符串满足以下条件,则称其为好字符串

  • 通过最多修改其中一个字符,可以将其变为回文串。

例如,aiwaiabcdcza 是好字符串,但 abcdatcoder 不是好字符串。请注意,回文串本身也是好字符串。

给定一个仅由英文小写字母组成的字符串 \(S\),请计算 \(S\) 中有多少个非空子串(连续子序列)是好字符串。

即使从 \(S\) 的不同位置取出的两个子串作为字符串是相等的,它们也会被分别计数。

数据范围

  • \(S\) 的长度在 \([1, 10^4]\) 范围内,且仅由小写英文字母组成

思路

对于本题,判断一个字符串是否是好字符串,可以直接用类似于判断回文串的方法,判断该字符串对称位置的字符不同的情况是否最多只出现一处。

考虑统计。如果通过常规方法,先 \(O(N^2)\) 枚举子串的左右端点,再 \(O(N)\) 检查回文串是否是好字符串,这个 \(O(N^3)\) 的复杂度是不可接受的。

但我们可以根据回文串的性质转换枚举的角度。由于回文串是中心对称的,如果我们能先 \(O(N)\) 枚举字符串的中点,然后从中点开始向两边同时扩张出去,一边 \(O(N)\) 枚举左右边界,一边判断对称位置的字符是否相同,就可以实现 \(O(N^2)\) 的统计了。

最后注意分奇偶性讨论回文串中点。

  • 没接触过这题的同学可以自行再做一做 [USACO 2025 January Bronze] Cow Checkups 这道题。

代码

void solve()
{
    string s;
    cin >> s;
    
    int n = s.size(), cnt = 0;
    
    // 假设回文串长度为奇数,枚举中点 i
    for(int i = 0; i < n; i++)
    {
        int l = i, r = i, diff = 0;
        // l, r 分别记录目前回文串的左右端点
        // diff 记录已经出现了多少个位置对应字符不同
        while(l >= 0 && r < n)
        {
            if(s[l] != s[r])
                diff++;
            if(diff <= 1) // 只要对称位置不同的数量还没超过 1 个,就还符合条件
                cnt++;
            l--; // 向两边扩张出去枚举
            r++;
        }
    }
    
    // 假设回文串长度为偶数,枚举中点为 i 和 i+1 之间的位置
    for(int i = 0; i < n - 1; i++)
    {
        int l = i, r = i + 1, diff = 0;
        while(l >= 0 && r < n)
        {
            if(s[l] != s[r])
                diff++;
            if(diff <= 1)
                cnt++;
            l--;
            r++;
        }
    }
    
    cout << cnt;
}

E - Sum of Average

  • 预估难度:普及+/提高-
  • 标签:枚举、数学、逆元、(二维)前缀和

题意

给定一个正整数 \(N\) 和一个长度为 \(N\) 的整数序列 \(A=(A_1,A_2,\ldots,A_N)\)

定义 \(f(l,r)\)\(A_l,A_{l+1},\ldots,A_r\) 的算术平均数。

请计算 \(\displaystyle \sum_{1\le l\le r\le N} f(l,r)\) 的值,结果对 \(998244353\) 取模。

数据范围

  • \(1\le N\le 5\times 10^5\)
  • \(0\le A_i \lt 998244353\)

思路

注意到区间算术平方数等于区间总和除以区间长度

我们可以借助一维前缀和,记 \(S_i = A_1 + A_2 + \dots + A_i\),来将求区间总和这一步优化至 \(O(1)\)

那么 \(f(l, r) = \dfrac{S_r - S_{l-1}}{r - l + 1}\)

但这样也还需要 \(O(N^2)\) 枚举区间的左右端点,时间复杂度不可接受。

考虑将视角转向枚举区间长度 \(L\),当长度确定时,算术平均数的除数部分就固定了。

考虑被除数部分,可以写成所有长度为 \(L\) 的区间总和:

\[\begin{aligned} &(S_L - S_0) + (S_{L+1} - S_1) + \dots + (S_N - S_{N-L}) \\ =& (S_L + S_{L+1} + \dots + S_N) - (S_0 + S_1 + \dots + S_{N-L}) \\ =& (S_L + S_{L+1} + \dots + S_N) - (S_1 + \dots + S_{N-L}) \end{aligned} \]

于是我们再对前缀和数组做一次前缀和,记 \(P_i = S_1 + S_2 + \ldots + S_i\),那么上式便可以写成:

\[\begin{aligned} &(P_N - P_{L-1}) - (P_{N-L} - P_0) \\ =&\ P_N - P_{L-1} - P_{N-L} \end{aligned} \]

那么长度为 \(L\) 的所有区间的算术平均数对答案的贡献即:

\[\frac{P_N - P_{L-1} - P_{N-L}}{L} \]

最终枚举长度+计算的时间复杂度 \(O(N)\),预处理 \(1 \sim N\) 每个整数的逆元时间复杂度 \(O(N)\)(公式法)或 \(O(N + \log N)\) (借助阶乘与阶乘逆元推导)。

代码

typedef long long ll;

ll qpow(ll a, ll n) // 快速幂
{
    ll r = 1;
    while(n)
    {
        if(n & 1)
            r = r * a % mod;
        a = a * a % mod;
        n >>= 1;
    }
}

ll fac[500005], inv[500005], d[500005];
// fac[i] 表示 i!   (%mod)
// inv[i] 表示 1/i! (%mod)
// d[i]   表示 1/i  (%mod)

void init(int N)
{
    fac[0] = 1;
    for(int i = 1; i <= N; i++)
        fac[i] = fac[i - 1] * i % mod;
    inv[N] = qpow(fac[N], mod - 2);
    for(int i = N - 1; i >= 0; i--)
        inv[i] = inv[i + 1] * (i + 1) % mod;
    
    // 1 / i == (i-1)! / i!
    for(int i = 1; i <= N; i++)
        d[i] = fac[i - 1] * inv[i] % mod;
}

int A[500005];
ll S[500005]; // S[i] 表示 A[1 ... i] 的总和
ll P[500005]; // P[i] 表示 S[1 ... i] 的总和

void solve()
{
    int N;
    cin >> N;
    init(N);
    
    for(int i = 1; i <= N; i++)
    {
        cin >> A[i];
        S[i] = (S[i - 1] + A[i]) % mod;
    }
    for(int i = 1; i <= N; i++)
        P[i] = (P[i - 1] + S[i]) % mod;
    
    ll ans = 0;
    for(int L = 1; L <= N; L++) // 枚举区间长度
    {
        ll sum = (P[N] - P[N - L]) - P[L - 1];
        sum = (sum % mod + mod) % mod;
        ans = (ans + sum * d[L]) % mod;
    }
    cout << ans;
}
posted @ 2026-07-25 22:04  StelaYuri  阅读(71)  评论(0)    收藏  举报