Skip to content

查看源代码:
CF2144.md

---
title: CF2144
createTime: 2025/9/17
categories:
    - IT
tags:
    - OI
---

## A

:::note 题意
给定一个数组,问能否分割成 $3$ 个非空段,使得每个非空段和 $\bmod 3$ 的值全部相同或全部不同。
:::

:::details code
```cpp
#include <bits/stdc++.h>
using namespace std;

inline bool check(int a, int b, int c) {
    return a==b&&b==c || a!=b&&b!=c&&c!=a;
}

const int maxn=64;
int s[maxn];

int main () {
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    
    int t;
    cin >> t;
    while (t--) {
        int n;
        cin >> n;
        for (int i=1; i<=n; i++) {
            int a;
            cin >> a;
            s[i] = s[i-1] + a;
        }
        
        for (int l=1; l<=n; l++) {
            for (int r=l+1; r<n; r++) {
                if (check(s[l]%3, (s[r]-s[l])%3, (s[n]-s[r])%3)) {
                    cout << l << ' ' << r << '\n';
                    goto end;
                }
            }
        }
        
        cout << "0 0\n";
        
        end:;
    }
    
    return 0;
}
```
:::

## B

:::note 题意
有一个排列,部分位置缺失。你要填入缺失的数字,使其成为一个完整排列。  
定义排列的代价为:使得整个排列有序,所需排序的最短连续子串的长度。  
在所有可能的填法中,最大化这个代价。
:::

设 $l$ 为已经归位的前缀长度,$r$ 为已经归位的后缀长度,显然代价为 $\max\{n-l-r,0\}$($\max$ 是为了解决 $l=r=n$ 的情况)

除了只有一个空位,已经能够确定的情况需要特判,总是有一种填空方法能够使一头一尾两个空不归位,此时代价最大。

:::details Code
```cpp
#include <bits/stdc++.h>
using namespace std;

const int maxn=2e5+10;
int p[maxn];
int cnt[maxn];

int main () {
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    
    int t;
    cin >> t;
    while (t--) {
        int n;
        cin >> n;
        
        for (int i=0; i<=n; i++) {
            cnt[i] = 0;
        }
        
        for (int i=1; i<=n; i++) {
            cin >> p[i];
            cnt[p[i]]++;
        }
        
        int l=0, r=0;
        
        for (int i=1; i<=n; i++) {
            if (p[i]==i || cnt[0]==1&&p[i]==0&&cnt[i]==0) {
                l++;
            } else {
                break;
            }
        }
        
        for (int i=n; i>=1; i--) {
            if (p[i]==i || cnt[0]==1&&p[i]==0&&cnt[i]==0) {
                r++;
            } else {
                break;
            }
        }
        
        cout << max(n-l-r, 0) << '\n';
    }
    
    return 0;
}
```
:::

## C

:::note 题意
给定两个序列 $a, b$,你可以任意交换 $a_i, b_i$(可以不交换),求使得 $a, b$ 不严格单调递增的方法数。
:::

依题意 DP,设 $f(i,0/1)$ 是 $1 \sim i$ 中最后一个不交换/交换的方法数。

设 $S_0(i) = a_{i-1} \le a_i \land b_{i-1} \le b_i$,  
$\quad S_1(i) = a_{i-1} \le b_i \land b_{i-1} \le a_i$

可得转移方程

$$
f(i,j) = S_{[j=1]}(i) f(i,0) + S_{[j=0]}(i) f(i,1)
$$

:::details
```cpp
#include <bits/stdc++.h>
using namespace std;

const int maxn=128, mod=998244353;
int a[maxn][2];
int f[maxn][2];

int main () {
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    
    int t;
    cin >> t;
    while (t--) {
        int n;
        cin >> n;
        
        for (int i=1; i<=n; i++) {
            f[i][0] = f[i][1] = 0;
        }
        
        for (int i=1; i<=n; i++) {
            cin >> a[i][0];
        }
        for (int i=1; i<=n; i++) {
            cin >> a[i][1];
        }
        
        f[0][0] = 1;
        for (int i=1; i<=n; i++) {
            for (int j=0; j<=1; j++) {
                for (int k=0; k<=1; k++) {
                    if (a[i-1][j]<=a[i][k] && a[i-1][!j]<=a[i][k]) {
                        f[i][k] += f[i-1][j];
                        f[i][k] %= mod;
                    }
                }
            }
        }
        
        cout << (f[n][0] + f[n][1]) % mod << '\n';
    }
    
    return 0;
}
```
:::

## D

:::note 题意

你的店铺现在要促销。具体而言,你要选择一个整数 $x > 1$,把价格 $c_i$ 变为 $\lceil c_i / x \rceil$。

接下来,你需要为商品贴上新的价格标签。旧的价格标签可以复用,打印一个新的价格标签需要花费 $y$。

求最大的利润。

:::

长见识了,原来整除分块还能这么出。

不难发现价格和标签成本二者的联动特别难求,所以枚举 $x \le \max\{2, \max c\}$ 是必然的。

用目标反过来表示原值,价格可以表示为

$$
\newcommand{\mset}[2]{\left\{ \kern{-0.3em} \begin{array}{c|c}
    #1 & #2
\end{array} \kern{-0.3em} \right\}}
\newcommand{\dmset}[2]{\mset{\displaystyle #1}{\displaystyle #2}}
\newcommand{\ceil}[1]{\left\lceil #1 \right\rceil}

\begin{align*}
\sum_{i=1}^{n} \ceil{\frac{c_i}{x}}
    &= \sum_{v=1}^{\max \ceil{c_i/x}} v\ \# \dmset{i}{ \ceil{\frac{c_i}{x}} = v } \\
    &= \sum_{v=1}^{\max \ceil{c_i/x}} v\ \# \dmset{i}{x(v-1) < c_i \le vx}
\end{align*}
$$

而重复标签数量可以如下计算:

$$
\newcommand{\mset}[2]{\left\{ \kern{-0.3em} \begin{array}{c|c}
    #1 & #2
\end{array} \kern{-0.3em} \right\}}
\newcommand{\dmset}[2]{\mset{\displaystyle #1}{\displaystyle #2}}
\newcommand{\ceil}[1]{\left\lceil #1 \right\rceil}

\begin{align*}
&\sum_{v=1}^{\max \ceil{c_i/x}} \max\left\{ 0, \#\dmset{i}{\ceil{\frac{c_i}{x}} = v} - \#\dmset{i}{c_i=v} \right\} \\
=& \sum_{v=1}^{\max \ceil{c_i/x}} \max\left\{ 0, \#\dmset{i}{x(v-1) < c_i \le vx} - \#\dmset{i}{c_i=v} \right\}
\end{align*}
$$

复杂度看上去很大,但实际上是一个调和级数的形式

$$T = \sum_{x=1}^{\max c} \frac{\max c}{x} = O(m \log m), \quad m = \max c$$

:::details Code
```cpp
#include <bits/stdc++.h>
using namespace std;

#define int long long

const int maxn=2e5+10, inf=0x3f3f3f3f3f3f3f3f;
int c[maxn];
int cnt[2*maxn];
int prec[2*maxn];

inline int ceildiv(int a, int b) {
    return (a + b - 1) / b;
}

signed main () {
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    
    int t;
    cin >> t;
    while (t--) {
        memset(cnt, 0, sizeof(cnt));
        
        int n, y;
        cin >> n >> y;
        
        int maxc=0;
        for (int i=1; i<=n; i++) {
            cin >> c[i];
            cnt[c[i]]++;
            maxc = max(maxc, c[i]);
        }
        
        for (int i=1; i<=2*maxc; i++) {
            prec[i] = prec[i-1] + cnt[i];
//          if (cnt[i]) printf("prec[%d]=%d\n", i, prec[i]);
        }
        
        int ans = -inf;
        for (int x=2; x<=max(2ll, maxc); x++) {
//          printf("at x=%d:\n", x);
            
            int p=0;
            for (int v=1; v<=ceildiv(maxc, x); v++) {
                int count = prec[v*x] - prec[x*(v-1)];
//              if (count) printf("\t%d in (%d,%d] turn to %d (%d - %d)\n", count, x*(v-1), v*x, v, prec[v*x], prec[x*(v-1)]);
                p += v * count;
                p -= y * max(0ll, count - cnt[v]);
            }
            
//          printf("tot: %d\n", p);
            ans = max(ans, p);
        }
        
        cout << ans << '\n';
    }
    
    return 0;
}
```
:::

## E

:::note 题意

给定一个序列 $h_i$,设它的前缀/后缀最大值集合分别为 $L, R$,求使得 $L, R$ 不变的子序列的数量 $\bmod 998244353$。

:::

把解拆成两部分:以 $i$ 结尾,包含前 $j$ 个前缀最大值的方案数 $f(i,j)$;以 $i$ 开头,包含前 $j$ 个后缀最大值的方案数 $g(i,j)$。

接下来看看怎么求 $f(i, j)$($g$ 是对称的)。

$f(i, j)$ 可以有三种来源:

1. 不选,无要求,有 $f(i-1,j)$ 转移;
2. 选且不影响 $j$,要求 $h_i \le L_j$,从 $f(i-1, j)$ 转移;
3. 选且影响 $j$,要求 $h_i = L_j$,从 $f(i-1, j)$ 转移。

于是

$$
f(i, j) = \begin{cases}
    f(i-1, j), &\quad h_i > L_j \\
    2f(i-1, j) + f(i-1, j-1), &\quad h_i = L_j\\
    2f(i-1, j), &\quad h_i < L_j
\end{cases}
$$

使用支持区间乘、单点加和单点查询的线段树维护 DP 值。

怎么把 $f, g$ 拼接在一起呢?

为了保证唯一性,枚举子序列中第一个最大值 $h_i=\max h$ ,则不难证明

$$\mathrm{ans} = \sum_{h_i = \max h} f(i-1, |L|-1)\ (g(i+1, |R|) + g(i+1, |R|-1))$$

总时间复杂度 $O(n \log n)$。

:::details Code (Easy Version)

感觉 Easy Version 是给懒得打线段树的人设计的。

```cpp
#include <bits/stdc++.h>
using namespace std;

const int maxn=5100, mod=998244353;

int lcnt, rcnt;
int l[maxn], r[maxn];
int h[maxn], f[maxn], g[maxn];
int ff[maxn], gg[maxn];

signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    
    int t;
    cin >> t;
    
    while (t--) {
        int n;
        cin >> n;
        
        int maxh = 0;
        for (int i=1; i<=n; i++) {
            cin >> h[i];
            maxh = max(maxh, h[i]);
            f[i] = g[i] = 0;
        }
        
        lcnt = rcnt = 0;
        for (int i=1; i<=n; i++) {
            if (h[i] > l[lcnt]) {
                l[++lcnt] = h[i];
            }
        }
        for (int i=n; i>=1; i--) {
            if (h[i] > r[rcnt]) {
                r[++rcnt] = h[i];
            }
        }
        
        f[0] = 1;
        ff[0] = f[lcnt-1];
        for (int i=1; i<=n; i++) {
            for (int j=lcnt; l[j]>=h[i]; j--) {
                f[j] = (f[j] << 1) % mod;
                if (l[j] == h[i]) {
                    f[j] = (f[j] + f[j-1]) % mod;
                }
            }
            ff[i] = f[lcnt-1];
        }
        
        g[0] = 1;
        for (int i=n; i>=1; i--) {
            gg[i] = (g[rcnt] + g[rcnt-1]) % mod;
            
            for (int j=rcnt; r[j]>=h[i]; j--) {
                g[j] = (g[j] << 1) % mod;
                if (r[j] == h[i]) {
                    g[j] = (g[j] + g[j-1]) % mod;
                }
            }
        }
        
        int ans=0;
        for (int i=1; i<=n; i++) {
            if (h[i] == maxh) {
                ans += ff[i-1] * (long long)gg[i] % mod;
                ans %= mod;
            }
        }
        
        cout << ans << '\n';
    }
    
    return 0;
}
```

:::

:::details Code (Hard Version)
```cpp
#include <bits/stdc++.h>
using namespace std;

#define int long long
const int maxn=3e5+10, mod=998244353;

int h[maxn];
int ff[maxn], gg[maxn];
int lcnt, rcnt;
int l[maxn], r[maxn];

struct segtree {
    int a[maxn * 4], lz[maxn * 4];
    
    inline int lc(int x) { return x << 1; }
    inline int rc(int x) { return (x << 1) | 1; }
    inline int md(int x, int y) { return (x + y) >> 1; }
    
    inline void clear() {
        a[1] = lz[1] = 0;
    }
    
    inline void pushdown(int tn) {
        int lcn = lc(tn), rcn = rc(tn);
        a[lcn] = a[lcn] * lz[tn] % mod;
        a[rcn] = a[rcn] * lz[tn] % mod;
        lz[lcn] = lz[lcn] * lz[tn] % mod;
        lz[rcn] = lz[rcn] * lz[tn] % mod;
        lz[tn] = 1;
    }
    
    inline int query(int tn, int tl, int tr, int x) {
        if (tl == tr) {
            return a[tn];
        }
        
        pushdown(tn);
        
        int mid = md(tl, tr);
        if (x <= mid) {
            return query(lc(tn), tl, mid, x);
        } else {
            return query(rc(tn), mid+1, tr, x);
        }
    }
    
    inline void add(int tn, int tl, int tr, int x, int y) {
        if (tl == tr) {
            a[tn] += y;
            a[tn] %= mod;
            return;
        }
        
        pushdown(tn);
        
        int mid = md(tl, tr);
        if (x <= mid) {
            add(lc(tn), tl, mid, x, y);
        } else {
            add(rc(tn), mid+1, tr, x, y);
        }
    }
    
    inline void mul2(int tn, int tl, int tr, int x, int y) {
        if (tr < x || y < tl) {
            return;
        }
        
        if (x <= tl && tr <= y) {
            a[tn] = (a[tn] << 1) % mod;
            lz[tn] = (lz[tn] << 1) % mod;
            return;
        }
        
        pushdown(tn);
        
        int mid = md(tl, tr);
        mul2(lc(tn), tl, mid, x, y);
        mul2(rc(tn), mid+1, tr, x, y);
    }
} f, g;

signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    
    int t;
    cin >> t;
    
    while (t--) {
        f.clear();
        g.clear();
        
        int n;
        cin >> n;
        
        int maxh = 0;
        for (int i=1; i<=n; i++) {
            cin >> h[i];
            maxh = max(maxh, h[i]);
        }
        
        lcnt = rcnt = 0;
        for (int i=1; i<=n; i++) {
            if (h[i] > l[lcnt]) {
                l[++lcnt] = h[i];
            }
        }
        for (int i=n; i>=1; i--) {
            if (h[i] > r[rcnt]) {
                r[++rcnt] = h[i];
            }
        }
        
        f.add(1, 0, lcnt, 0, 1);
        for (int i=1; i<=n; i++) {
            ff[i-1] = f.query(1, 0, lcnt, lcnt - 1);
            
            int j0 = lower_bound(l+1, l+lcnt+1, h[i]) - l;
//          printf("j0: %lld\n", j0);
            
            int add = f.query(1, 0, lcnt, j0-1);
            f.mul2(1, 0, lcnt, j0, lcnt);
            if (l[j0] == h[i]) {
                f.add(1, 0, lcnt, j0, add);
            }
            
//          for (int j=0; j<=lcnt; j++) {
//              printf("f(%lld, %lld) = %lld\n", i, j, f.query(1, 0, lcnt, j));
//          }
        }
        
        g.add(1, 0, rcnt, 0, 1);
        for (int i=n; i>=1; i--) {
            gg[i] = (g.query(1, 0, rcnt, rcnt) + g.query(1, 0, rcnt, rcnt-1)) % mod;
            
            int j0 = lower_bound(r+1, r+rcnt+1, h[i]) - r;
//          printf("j0: %lld\n", j0);
            
            int add = g.query(1, 0, rcnt, j0-1);
            g.mul2(1, 0, rcnt, j0, rcnt);
            if (r[j0] == h[i]) {
                g.add(1, 0, rcnt, j0, add);
            }
            
//          for (int j=0; j<=rcnt; j++) {
//              printf("g(%lld, %lld) = %lld\n", i, j, g.query(1, 0, rcnt, j));
//          }
        }
        
        int ans=0;
        for (int i=1; i<=n; i++) {
            if (h[i] == maxh) {
                ans += ff[i-1] * gg[i] % mod;
                ans %= mod;
            }
        }
        
        cout << ans << '\n';
    }
    
    return 0;
}
```
:::