【MX-S12】梦熊 NOIP 2025 模拟赛 4

P14566 【MX-S12-T1】取模

最开始盲猜答案等于 \(\max(a_i)-\min(a_i)\),发现只有 \(20pts\),思考一下为什么不行。

我们发现当 \(p>\max(a_i)\) 时,答案为 \(\max(a_i)-\min(a_i)\)。

那如果 \(p\le max(a_i)\) 呢?

假设序列 \(a\) 的 严格次大值 为 \(x\),若 \(p\in (x,\max(a)]\),答案为 \(x\)。

若 \(p\le x\),答案一定不回大于 \(x\),所以最后答案等于 \(\max(x,\max(a_i)-\min(a_i))\)。

注意,当 \(a_i\) 全部相等时,答案为 \(0\),此时 \(x=0\)。

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

void File(){
  freopen("1.in", "r", stdin);
}

const int kN = 1e5 + 7;

int n, a[kN];
void solve(){
  cin >> n;
  for(int i = 1; i <= n; i++) cin >> a[i];
  bool f = 1;
  int cmx = 0;
  sort(a + 1, a + n + 1);
  for(int i = 1; i <= n; i++) if(a[i] != a[n]) cmx = a[i];
  cout << max(cmx, a[n] - a[1]) << '\n';
}
signed main(){
  File();
  int T = 1; cin >> T;
  while(T--) solve();
  return 0;
}

P14567 【MX-S12-T2】区间

首先注意到 \(f\) 序列 单调不降,这意味着如果存在两个合法区间 \([l1,r1]\subset [l2,r2]\),则 \([l2,r2]\) 的权值一定 \(>[l1,r1]\) 的权值,所以我们可以认为只有 极小区间 是有用的。

再仔细思考,发现任意合法的 极小区间 不可能相交。考虑反证法,假设有两个 极小区间 \([l1,r1],[l2,r2]\) 相交,那么相交的部分为 \([l2,r1]\)。因为 \([l1,r1],[l2,r2]\) 为 极小区间,所以 \([l1,l2)\) 中必有和 \([l2,r1]\) 中有相同的颜色,同理 \((r1,r2]\) 中必有和 \([l2,r1]\) 中有相同的颜色,那么这样 \([l1,r1],[l2,r2]\) 就都不是合法区间了。

有了上面的这些性质,接下来的就好做了。

记 \(mn_c\) 和 \(mx_c\) 分别表示颜色 \(c\) 最靠左的点和最靠右的点。

我们对于每个左端点 \(l\) 求出以 \(l\) 为左端点的合法的极小区间 \([l,r]\)。考虑从右到左扫描 \(l\),如果 \(\exists c,mn_c<l\),那么颜色 \(c\) 是不能选的,考虑用单调栈维护所有不合法的右端点,那么每次应该满足 \(r<stk_{top}\)。此外,我们还有限制 \(mn_c\le r,mx_c \le r\),考虑令 \(mx_c\) 为 \(1\),\(mn_c\) 为 \(-1\),相当于要满足区间和为 \(0\),记录后缀和开桶记录。

记 \(r_i\) 表示以 \(i\) 为左端点,合法的 极小区间 为 \([i,r_i]\)。特别的,如果不存在合法区间,则 \(r_i=0\)。

求出 \([l,r_i]\) 后,先去掉不是 极小区间 的 \([i,r_i]\)。由于我们上面得到性质这些区间不相交,所以我们暴力枚举每个 \([i,r_i]\) 即可,复杂度 \(\Theta(n)\)。

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

void File(){
  freopen("1.in", "r", stdin);
}

using ll = long long;
const int kN = 1e6 + 7;

int n, c[kN], v[kN], f[kN], mn[kN], mx[kN], lst[kN], l[kN], r[kN], stk[kN], top, ban[kN];
ll ans = 0;
signed main(){
  File();
  ios::sync_with_stdio(0), cin.tie(0);
  cin >> n;
  for(int i = 1; i <= n; i++) cin >> c[i];
  for(int i = 1; i <= n; i++) cin >> v[i];
  for(int i = 1; i <= n; i++) cin >> f[i];
  for(int i = 1; i <= n; i++) mx[c[i]] = i;
  for(int i = n; i >= 1; i--) mn[c[i]] = i;
  int s = 0; lst[0] = n + 1;
  for(int i = n; i >= 1; i--){
    s += (mx[c[i]] == i) - (mn[c[i]] == i); stk[++top] = i;
    while(top && mn[c[stk[top]]] >= i) top--;
    if((!top || lst[s] <= stk[top]) && lst[s]) r[i] = lst[s] - 1, l[r[i]] = max(l[r[i]], i);
    lst[s] = i;
  } int R = n + 1; 
  for(int i = n; i >= 1; i--){
    if(!r[i]) continue;
    if(r[i] >= R) ban[i] = 1;
    else R = r[i];
  }
  ans = 1e18;
  for(int i = 1; i <= n; i++){
    if(!ban[i] && r[i]){
      ll res = 0;
      for(int j = i; j <= r[i]; j++) res += 1ll * v[j] * f[j - i + 1];
      ans = min(ans, res);
    }
  }
  cout << ans << '\n';
  return 0;
}

P14568 【MX-S12-T3】排列

一开始想歪了,想着图论建模然后图计数去了。

其实这题还是非常清晰的,排列,相对大小,应该能想到插入 DP。

考虑有一个排名序列,我们当前在考虑第 \(i\) 个元素 \(a_i\)。如果 \(op_i=0/1\),我们需要将 \(a_i\) 放到当前序列的最前面或最后面;如果 \(op_i=2/3\),我们需要保证后面的元素全部在当前元素的右边或左边。

容易发现,我们当前关心这个 \(a_i\) 能插入哪些位置。

设 \(f_{i,j}\) 表示插入前 \(i\) 个 数,还有 \(j\) 个空位的方案数。

有转移:

\[f_{i,j}\gets \begin{cases} f_{i-1,j-1},op_i=0/1 & \\ \sum_{k\ge j}f_{i-1,k},op_i=2/3\end{cases} \]

直接后缀和优化可以做到 \(\Theta(n^2)\)。

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

using ll = long long;
const ll mod = 998244353;
const int kN = 5003;

void File(){freopen("permutation6.in", "r", stdin);freopen("permutation.out", "w", stdout);}
void Add(ll &x, ll y){(x += y) >= mod ? x -= mod : 0;}
ll add(ll x, ll y){Add(x, y); return x;}

int n, op[kN];
ll f[kN][kN], s[kN];
signed main(){
  File();
  cin >> n;
  for(int i = 1; i <= n; i++) cin >> op[i];
  bool fg = 0;
  for(int i = 1; i <= n; i++){
    if(op[i] >= 2){
      for(int j = i + 1; j <= n; j++){
        if(op[j] == op[i] - 2) fg = 1;
      }
    }
  }
  if(fg){
    cout << 0 << '\n';
    return 0;
  }
  f[0][0] = 1;
  for(int i = 1; i <= n; i++){
    for(int j = n; j >= 0; j--) s[j] = add(s[j + 1], f[i - 1][j]);
    if(op[i] == 0 || op[i] == 1) for(int j = 0; j <= i; j++) Add(f[i][j], f[i - 1][j - 1]);
    else for(int j = 0; j <= i; j++) Add(f[i][j], s[j]);
  }
  ll ans = 0;
  for(int j = 0; j <= n; j++) Add(ans, f[n][j]);
  cout << ans << '\n';
  return 0;
}

P14569 【MX-S12-T4】Sea, you again

首先,我们要知道 \(f_B(n)\) 等于多少。有一个贪心是从低位到高位,每次相邻两项能合并就合并。

其次,看到这道题,我们就不难想到用数位 DP 来做,但是感觉不太好下手。

这样,我们先考虑特殊性质 \(B\) 的做法。

我们从低位到高位数位 DP,设状态 \(f_{i,j,k}\) 和 \(g_{i,j,k}\) 分别表示当前考虑到第 \(i\) 位,当前这一段的和为 \(j\),总共分为了 \(k\) 段的权值和(不考虑当前这一段)和方案数。

有转移:

\[f_{i,j,k}=\sum_{h=0}^j f_{i-1,h,k}+\sum_{h=B-j}^{B-1} (f_{i-1,h,k-1} + h\times B^{k-1}\times g_{i-1,h,k-1}) \]

\[g_{i,j,k}=\sum_{h=0}^j g_{i-1,h,k}+\sum_{h=B-j}^{B-1} g_{i-1,h,k-1} \]

答案为:

\[\sum_k \sum_{j=0}^{B-1}(f_{n,j,k}+g_{n,j,k}\times j \times B^k) \]

上面这个时间复杂度直接爆炸。

最关键的一步来了,我们发现 k 这一维是可以消掉的。

具体的,我们另 \(f_{i,j}=\sum_k f_{i,j,k}\),\(g_{i,j}=\sum_{k}B^k\times g_{i,j,k}\)。

于是我们得到转移:

\[f_{i,j}=\sum_{h=0}^j f_{i-1,h}+\sum_{h=B-j}^{B-1}(f_{i-1,h} + h\times g_{i-1,h}) \]

\[g_{i,j}=\sum_{h=0}^j g_{i-1,h}+\sum_{h=B-j}^{B-1}(B\times g_{i-1,h}) \]

答案为:

\[\sum_{j=0}^{B-1}(f_{n,j}+g_{n,j}\times j) \]

加个滚动数组和前缀和优化一下。

再考虑正解,发现只需要加个 \(lim\),表示当前位置是否给下一位限制。

这部分就是 dirty work。

最后复杂度 \(\Theta(\sum nB)\)。

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

using ll = long long;
const int kN = 1e6 + 7;
const ll mod = 998244353;

void File(){
  freopen("pocket.in", "r", stdin);
  freopen("pocket.out", "w", stdout);
}
void Add(ll &x, ll y){(x += y) >= mod ? x -= mod : 0;}
ll add(ll x, ll y){Add(x, y); return x;}

int B, nL, nR, l[kN], r[kN];
ll f[2][kN][2], g[2][kN][2];
ll sf[kN][2], sg[kN][2], sg2[kN][2];
// 当前这一段和为 j,第 i 个位置是否给下一位限制

ll calc(int n, int dig[]){
  for(int i = 0; i < 2; i++){
    for(int j = 0; j < B; j++){
      f[i][j][0] = f[i][j][1] = 0;
      g[i][j][0] = g[i][j][1] = 0;
    }
  }
  int x = 0;
  g[0][0][0] = 1;
  for(int i = 0; i < n; i++){
    sf[0][0] = f[x][0][0], sg[0][0] = g[x][0][0], sg2[0][0] = 0;
    sf[0][1] = f[x][0][1], sg[0][1] = g[x][0][1], sg2[0][1] = 0;
    for(int j = 1; j < B; j++){
      for(int k = 0; k < 2; k++){
        sf[j][k] = add(f[x][j][k], sf[j - 1][k]);
        sg[j][k] = add(g[x][j][k], sg[j - 1][k]);
        sg2[j][k] = add(g[x][j][k] * 1ll * j % mod, sg2[j - 1][k]);        
      }
    }
    x ^= 1;
    for(int j = 0; j < B; j++){
      for(int k = 0; k < 2; k++){
        f[x][j][k] = g[x][j][k] = 0;
      }
    }
    for(int j = 0; j < B; j++){
      if(j < dig[i]){
        //一定不会对下一位有限制
        for(int k = 0; k < 2; k++){
          Add(f[x][j][0], sf[j][k]);
          Add(f[x][j][0], (mod + sf[B - 1][k] - sf[B - j - 1][k]) % mod);
          Add(f[x][j][0], (mod + sg2[B - 1][k] - sg2[B - j - 1][k]) % mod);
          Add(g[x][j][0], sg[j][k]);
          Add(g[x][j][0], (mod + sg[B - 1][k] - sg[B - j - 1][k]) % mod * 1ll * B % mod);
        }
      }
      else{
        // 不新开一段
        for(int k = 0; k < 2; k++){
          Add(f[x][j][0], (mod + sf[j][k] - sf[j - dig[i]][k]) % mod);
          Add(g[x][j][0], (mod + sg[j][k] - sg[j - dig[i]][k]) % mod);
        }
        Add(f[x][j][0], f[x ^ 1][j - dig[i]][0]);
        Add(g[x][j][0], g[x ^ 1][j - dig[i]][0]);
        Add(f[x][j][1], sf[j - dig[i]][1]);
        Add(g[x][j][1], sg[j - dig[i]][1]);
        if(j > dig[i]) Add(f[x][j][1], sf[j - dig[i] - 1][0]);
        if(j > dig[i]) Add(g[x][j][1], sg[j - dig[i] - 1][0]);
        // 新开了一段
        if(j > dig[i]){
          for(int k = 0; k < 2; k++){
            Add(f[x][j][1], (mod + sf[B - 1][k] - sf[B - j - 1][k]) % mod);
            Add(f[x][j][1], (mod + sg2[B - 1][k] - sg2[B - j - 1][k]) % mod);
            Add(g[x][j][1], (mod + sg[B - 1][k] - sg[B - j - 1][k]) % mod * 1ll * B % mod);
          }
        }
        else{
          for(int k = 0; k < 2; k++){
            Add(f[x][j][k], (mod + sf[B - 1][k] - sf[B - j - 1][k]) % mod);
            Add(f[x][j][k], (mod + sg2[B - 1][k] - sg2[B - j - 1][k]) % mod);
            Add(g[x][j][k], (mod + sg[B - 1][k] - sg[B - j - 1][k]) % mod * 1ll * B % mod);
          }
        }
      }
    }
  }
  ll ans = 0;
  for(int j = 0; j < B; j++){
    Add(ans, f[x][j][0]);
    Add(ans, 1ll * j * g[x][j][0] % mod);
  }
  return ans;
}
void solve(){
  cin >> B >> nL;
  for(int i = 0; i < nL; i++) cin >> l[i];
  cin >> nR;
  for(int i = 0; i < nR; i++) cin >> r[i];
  if(nL != 1 || (nL == 1 && l[0] != 0)){
    l[0]--;
    for(int i = 0; i < nL; i++){
      if(l[i] < 0) l[i] += B, l[i + 1]--;
    }
    if(l[nL - 1] == 0) nL--;
  }
  cout << (mod + calc(nR, r) - calc(nL, l)) % mod << '\n';
  return ;
}

signed main(){
  File();
  ios::sync_with_stdio(0), cin.tie(0);
  int T = 1; cin >> T;
  while(T--) solve();
  return 0;
}
posted @ 2026-08-29 15:57  CJ-rnd  阅读(15)  评论(0)    收藏  举报