梦熊NOIP2025模拟赛4 赛后总结
A. 【MX-S12-T1】取模
给定一个长度为 \(n\) 的非负整数序列 \(a_1, \ldots, a_n\)。请你选取一个正整数 \(p\),然后将 \(a\) 中的每个数除以 \(p\) 得到的余数放进一个新数组 \(b\) 中,即 \(b_i = a_i \bmod p\),你的得分就是 \(b\) 中最大值与最小值的差。
求出你能得到的最大得分。
将 \(a\) 排序并去重,因为重复的数取模之后还是一样。
如果我们选的 \(p>a_n\),所有数取模之后都不变,此时得分是 \(a_n-a_1\)。
如果我们选了一个 \(p\le a_n\),不妨设 \(<p\) 的最大数是 \(a_k\),则最后的得分最大不超过 \(a_k\),且能在 \(p\) 取到 \(a_{k+1}\) 的时候取到得分 \(a_k\)。所以我们可以取 \(p=a_n\),此时得分为 \(a_{n-1}\)。
于是所有情况的最大得分是 \(\max(a_{n-1}, a_n-a_1)\)。
void work() {
int n; cin >> n;
vector<int> a(n);
for (int i = 0; i < n; ++i)
cin >> a[i];
sort(a.begin(), a.end());
a.erase(unique(a.begin(), a.end()), a.end());
n = a.size();
if (n == 1) return cout << 0 << endl, void();
int ans = max(a[n - 1] - a[0], a[n - 2]);
cout << ans << endl;
}
B. 【MX-S12-T2】区间
给定三个长度为 \(n\) 的正整数序列:颜色序列 \(c\),权值序列 \(v\),代价序列 \(f\)。序列的下标均由 \(1\) 开始标号。保证代价序列单调不减,即 \(f_i \le f_{i + 1}\)。
对一个区间 \([l, r]\)(\(1 \le l \le r \le n\))做如下定义:
- 称区间 \([l, r]\) 合法,当且仅当:不存在一种颜色 \(x\) 在区间内外均出现过,即不存在颜色 \(x\) 和下标 \(i, j\) 满足 \(c_i = c_j = x\) 且 \(i \in [l, r]\)、\(j \notin [l, r]\)。
- 区间 \([l, r]\) 的价值定义为 \(\displaystyle \sum_{i = l}^{r} (v_i \cdot f_{i - l + 1})\)。
找出一个价值最小的合法区间,你只需要求出该最小价值。
首先一个关键的性质是,如果两个区间 \([l_1,r_1],[l_2,r_2]\) 满足 \([l_2, r_2]\subset [l_1,r_1]\),则显然 \([l_2,r_2]\) 一定比 \([l_1,r_1]\) 优,这是由 \(v_i,f_i\) 非负且 \(f_i\) 单调不降保证的:
\([l_1,r_1]\) 的价值是 \(\sum\limits_{i=l_1}^{r_1} (v_i\times f_{i-l_1+1})\),\([l_2,r_2]\) 的价值是 \(\sum\limits_{i=l_2}^{r_2} (v_i\times f_{i-l_2+1})\)。由于 \([l_2,r_2]\) 被包含在了 \([l_1,r_1]\) 中,把 \(i\in [l_2,r_2]\) 的 \(f_{i-l_2+1}\) 换为 \(f_{i-l_1+1}\),这一步操作价值一定不降(因为 \(l_2\ge l_1\),所以 \(f_{i-l_2+1}\ge f_{i-l_1+1}\))。此时,\([l_2,r_2]\) 的价值就严格不低于 \([l_1,r_1]\)。故原先的 \([l_2,r_2]\) 一定比 \([l_1,r_1]\) 小。
然后我们通过一个哈希来确定所有好区间:对于每个颜色 \(i\) 我们随机一个权值 \(w_i\)。然后对于每一个位置 \(x\),如果位置 \(x\) 是他的颜色里第一次出现,则将这个位置的权值赋为 \(-(cnt_{c_x}-1)\times w_{c_x}\),否则将他的权值赋为 \(w_{c_x}\)。然后做一个前缀和 \(t_{i}=\sum\limits_{j=1}^i w_i\)。
然后对于每一个位置 \(i\),找到上一个 \(t_j=t_i\) 的位置,则 \([j+1,i]\) 就是一个合法的区间。我们把所有这样的区间都拿出来,然后扫描线 \(r\) 用树状数组去个重。最后剩下来的区间一定是互不相交的(否则一定有一个颜色被两个区间包含,要么不优要么不合法),所以直接暴力算贡献即可。
最终时间复杂度 \(O(n \log n)\)。
using ull = unsigned long long;
const int MAXN = 1e6 + 5;
int c[MAXN], v[MAXN], f[MAXN], n;
int cnt[MAXN], fr[MAXN];
ull t[MAXN], w[MAXN];
struct _bit {
int tr[MAXN];
int lowbit(int x) { return (x & (-x)); }
void modify(int x, int k) {
while (x <= n) {
tr[x] += k;
x += lowbit(x);
}
}
int query(int x) {
int ret = 0;
while (x) {
ret += tr[x];
x -= lowbit(x);
}
return ret;
}
} bit;
void work() {
mt19937_64 rnd(11451419);
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)
w[i] = rnd();
for (int i = 1; i <= n; ++i) {
cnt[c[i]]++;
if (!fr[c[i]]) fr[c[i]] = i;
}
for (int i = 1; i <= n; ++i) {
if (i == fr[c[i]]) t[i] = w[c[i]] * (cnt[c[i]] - 1);
else t[i] = -w[c[i]];
}
for (int i = 1; i <= n; ++i)
t[i] += t[i - 1];
map<int, int> mp;
mp[0] = 0;
vector<pair<int, int>> v1;
for (int i = 1; i <= n; ++i) {
if (mp.find(t[i]) != mp.end()) {
v1.push_back(make_pair(mp[t[i]] + 1, i));
}
mp[t[i]] = i;
}
sort(v1.begin(), v1.end(), [](auto x, auto y) {
if (x.second == y.second) return x.first > y.first;
return x.second < y.second;
});
vector<pair<int, int>> v2;
for (auto [l, r]:v1) {
if (bit.query(n) - bit.query(l - 1) > 0) continue;
bit.modify(l, 1);
v2.push_back(make_pair(l, r));
}
int ans = LLONG_MAX;
for (auto [l, r]:v2) {
int tmp = 0;
for (int i = l; i <= r; ++i) {
tmp += v[i] * f[i - l + 1];
}
ans = min(ans, tmp);
}
cout << ans << endl;
}
C. 【MX-S12-T3】排列
求出有多少个 \(1 \sim n\) 的排列 \(a_1, \ldots, a_n\) 满足以下条件:
对于每个下标 \(i\)(\(1 \le i \le n\)),
- \(a_i < \min_{j < i} a_j\) 或
- \(a_i > \max_{j < i} a_j\) 或
- \(a_i < \min_{j > i} a_j\) 或
- \(a_i > \max_{j > i} a_j\)。
对每个下标 \(i\),其满足四种情况中的哪一种由输入给出。答案对 \(998244353\) 取模。
注:约定 \(0\) 个数的 \(\min\) 为 \(+\infty\),\(0\) 个数的 \(\max\) 为 \(-\infty\)。
将所有限制分为两类,一种是对前缀的限制,一种是对后缀的限制。发现这两种限制自己之内的顺序是确定的,我们现在要计数他们之间的顺序情况。
不妨设限制是前缀的点之间的大小关系是 \(a_1,a_2,\cdots a_n\),后缀的点之间的大小关系为 \(b_1,b_2,\cdots b_n\)。我们现在需要在他们之间填数。
然后设 \(f_{i,j}\) 表示填了 \(a\) 的前 \(i\) 个和 \(b\) 的前 \(j\) 个,转移考虑谁填了 \(k=i+j\) 这个数。分为四种情况讨论:
- 如果要填 \(a_i\) 这个位置,则:
- 如果 \(i\) 是前缀 \(\min\),则 \(a_i\) 这个位置前面不能填过数,即不能存在 \(k\le j, b_k <a_i\)。
- 如果 \(i\) 是前缀 \(\max\),则 \(a_i\) 位置前不能有空位,即不存在 \(k> j, b_k < a_i\)。
- 如果要填 \(b_j\),则:
- 如果 \(j\) 是后缀 \(\min\),则不存在 \(k\le i,a_k>b_j\)。
- 如果 \(j\) 是后缀 \(\max\),则不存在 \(k>i,a_k>g_j\)。
这个东西你暴力 check 是 \(O(n)\) 的,但是但凡是一个人类都会用前缀和优化这个。
const int mod = 998244353, MAXN = 5e3 + 5;
int n, f[MAXN][MAXN], op[MAXN];
deque<int> a, b;
void add(int &x, int y) {
x += y;
if (x >= mod) x -= mod;
}
void work() {
cin >> n;
for (int i = 1; i <= n; ++i) {
cin >> op[i];
}
for (int i = 1; i <= n; ++i) {
if (op[i] == 0) a.push_front(i);
else if (op[i] == 1) a.push_back(i);
}
for (int i = n; i >= 1; --i) {
if (op[i] == 2) b.push_front(i);
else if (op[i] == 3) b.push_back(i);
}
int na = a.size(), nb = b.size();
a.push_front(0); b.push_front(0);
vector<int> amxp(n + 2), amxs(n + 2);
vector<int> bmnp(n + 2), bmns(n + 2);
for (int i = 1; i <= na; ++i)
amxp[i] = max(amxp[i - 1], a[i]);
for (int i = na; i >= 1; --i)
amxs[i] = max(amxs[i + 1], a[i]);
bmnp[0] = INT_MAX; bmns[nb + 1] = INT_MAX;
for (int i = 1; i <= nb; ++i)
bmnp[i] = min(bmnp[i - 1], b[i]);
for (int i = nb; i >= 1; --i)
bmns[i] = min(bmns[i + 1], b[i]);
f[0][0] = 1;
for (int i = 0; i <= na; ++i) {
for (int j = 0; j <= nb; ++j) {
if (i) {
if (op[a[i]] == 0) {
if (a[i] < bmnp[j]) add(f[i][j], f[i - 1][j]);
} else if (op[a[i]] == 1) {
if (a[i] < bmns[j + 1]) add(f[i][j], f[i - 1][j]);
}
}
if (j) {
if (op[b[j]] == 2) {
if (amxp[i] < b[j]) add(f[i][j], f[i][j - 1]);
} else if (op[b[j]] == 3) {
if (amxs[i + 1] < b[j]) add(f[i][j], f[i][j - 1]);
}
}
}
}
cout << f[na][nb] << endl;
}

浙公网安备 33010602011771号