2025/7/3 cw模拟赛总结
祝自己生日快乐!
T1
打表推结论发现是循环的,只需要判 \(m\) 的奇偶性输出答案。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 4000010;
int n, m, a[N], T[N], b[N];
signed main() {
// freopen ("ex_a3.in", "r", stdin);
// freopen ("a.out", "w", stdout);
scanf ("%lld %lld", &n, &m);
for (int i = 1; i <= n; i ++)
scanf ("%lld", &a[i]);
for (int i = 1; i <= n; i ++)
T[a[i]]++, b[i] = T[a[i]];
memset (T, 0, sizeof(T));
for (int i = 1; i <= n; i ++)
T[b[i]]++, a[i] = T[b[i]];
if (m & 1) {
for (int i = 1; i <= n; i ++)
printf ("%lld%c", b[i], " \n"[i == n]);
}
else {
for (int i = 1; i <= n; i ++)
printf ("%lld%c", a[i], " \n"[i == n]);
}
return 0;
}
/*
10 2
1 3 2 4 1 3 1 4 2 1
10 4
1 3 2 4 1 3 1 4 2 1
12 4
1 3 4 2 2 5 5 3 4 4 3 1
*/
T2
考虑给原序列按每次操作分组。
记 \(f_{i,a,b}\) 表示第 \(i\) 组操作完删掉了 \(c,d,e\),还剩 \(a,b\) 的最大答案,因为顺序不影响答案故总有 \(f_{i,a,b} = f_{i,b,a}\)。初始化 \(f_{1,v_1,v_2} = f_{1,v_1,0} = f_{1,v_2,0} = f_{1,0,0} = 0\),其余为 \(-\infty\)。
考虑分情况讨论:
-
\(f_{i,a,b} \leftarrow f_{i-1,a,b} + [c = d = e]\),\(a,b\) 全都剩下了。
-
\(f_{i,a,e} \leftarrow f_{i-1,a,b} + [b = c = d]\),\(a,b\) 中剩下一个,\(c,d,e\) 中剩下一个(即 \(f_{i,a,c},f_{i,a,d},f_{i,b,c},f_{i,b,d},f_{i,b,e}\) 这些的转移也同理)。
-
\(f_{i,c,d} \leftarrow f_{i-1,a,b} + [a = b = e]\),\(c,d,e\) 中剩下两个(即 \(f_{i,c,e},f_{i,d,e}\) 的转移同理)。
但是这样朴素递推是 \(O(n^3)\),考虑如何优化。你发现这玩意好像能用的常用优化只有矩阵快速幂,但是好像不是很好做。
考虑系数的本质:
-
如果说 \([c = d = e]\) 需要或者不需要,都与我们的 \(a,b\) 无关,要加直接加即可。
-
如果说 \([b = c = d]\) 需要,那么改变的 dp 值只有 \(f_{i,a,e} \leftarrow f_{i-1,a,c} + 1\),否则本质是在求 \(f_{i-1,a,b}\) 的 \(b\) 的变化之后的 \(\max\),两者都是 \(O(n^2)\)。
-
如果说 \([a = b = e]\) 需要,你发现 \(a,b\) 一定,\(c,d\) 一定,就是 \(O(1)\) 的转移。
最后答案为 \(f_{n,a,b} + [a = b = v_{3n}]\)。
那么这样下来就是 \(O(n^2)\) 的时间复杂度,空间的话滚动数组优化即可,同时我们可以把所有上一次状态存进队列之后统一转移。
#include <bits/stdc++.h>
#define int long long
using namespace std;
constexpr int N = 3010;
int n, v[N * 3], f[N][N], ultra, ans;
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n;
for (int i = 1; i <= n * 3; i ++) cin >> v[i];
memset (f, ~0x3f, sizeof(f));
f[v[1]][v[2]] = f[v[2]][v[1]] = f[v[1]][0] = f[v[2]][0] = f[0][0] = 0;
queue<pair<int,pair<int,int>>> q;
for (int i = 2; i <= n; i ++) {
int c = v[i * 3 - 3], d = v[i * 3 - 2], e = v[i * 3 - 1], cnt = 0;
// f[i][c][d]
q.push({c, {d, f[e][e] + 1}});
q.push({c, {d, f[0][0]}});
q.push({c, {e, f[d][d] + 1}});
q.push({c, {e, f[0][0]}});
q.push({d, {e, f[c][c] + 1}});
q.push({d, {e, f[0][0]}});
// f[i][a][b]
if (c == d && d == e) {
cnt++, ultra++;
}
// f[i][a][e]
for (int a = 1; a <= n; a ++) {
if (c == d) {
q.push({a, {e, f[a][c] + 1}});
}
q.push({a, {e, f[a][0]}});
if (d == e) {
q.push({a, {c, f[a][d] + 1}});
}
q.push({a, {c, f[a][0]}});
if (c == e) {
q.push({a, {d, f[a][e] + 1}});
}
q.push({a, {d, f[a][0]}});
}
while (!q.empty()) {
auto p = q.front(); q.pop();
int a = p.first, b = p.second.first, w = p.second.second;
w -= cnt;
f[a][b] = f[b][a] = max(f[a][b], w);
f[a][0] = max(f[a][0], w);
f[b][0] = max(f[b][0], w);
f[0][0] = max(f[0][0], w);
}
}
ans = -1;
for (int a = 1; a <= n; a ++) {
for (int b = a; b <= n; b ++)
ans = max(ans, f[a][b] + (a == b && b == v[3 * n]));
}
cout << ans + ultra << '\n';
return 0;
}
T3
首先看到这类题可能会想拿二维树形数据结构去做,或者说可以扫描线,但显然对于所有情况求解并不能单纯用 DS 做,你发现 \(n \leq 400\) 考虑 \(O(n^3)\) 的做法。
记 \(f_{l,r,h}\) 表示下边界为 \(h\),左右边界为 \(l,r\) 时的最优上边界,那么转移必须是 \(O(1)\) 的,为了不重不漏考虑类似区间 dp 左右扩张,小区间转大区间。
那么 \(f_{l,r,h}\) 就可以从 \(f_{l+1,r,h}\) 和 \(f_{l,r-1,h}\) 转移而来,但是会有新出现的情况。对于新扩张的第 \(l\) 列新的需要考虑的元素为 \(a_{h,l}\),那么中间都满足只需要考虑它在第 \(r\) 列的出现情况,为了不取到重复那么只能取到它在第 \(r\) 列出现的最大的行。\(a_{h,r}\) 也同理。那么记 \(to_{i,c}\) 表示 \(c\) 这个值在第 \(i\) 列出现的最大行数,每次动态更新。
紧接着因为每次一定是从 \(f_{l,r,h-1} \to f_{l,r,h}\),滚动数组优化空间滚掉最后一位,那么最后转移长这样:
答案的话每次转移完更新:
#include <bits/stdc++.h>
using namespace std;
const int N = 410, M = 160010;
int n, m, a[N][N], f[N][N], to[N][M], t[N][N], ans;
signed main() {
scanf ("%d %d", &n, &m);
for (int i = 1; i <= n; i ++) {
for (int j = 1; j <= m; j ++)
scanf ("%d", &a[i][j]);
}
for (int i = 1; i <= n; i ++) {
for (int j = 1; j <= m; j ++)
f[j][j] = to[j][a[i][j]];
for (int j = 1; j <= m; j ++)
to[j][a[i][j]] = i;
for (int len = 2; len <= m; len ++) {
for (int l = 1; l + len - 1 <= m; l ++) {
int r = l + len - 1;
f[l][r] = max({f[l + 1][r], f[l][r - 1], to[l][a[i][r]], to[r][a[i][l]]});
}
}
for (int l = 1; l <= m; l ++) {
for (int r = l; r <= m; r ++)
t[l][r] = max(t[l][r], f[l][r]);
}
for (int l = 1; l <= m; l ++) {
for (int r = l; r <= m; r ++)
ans = max(ans, (r - l + 1) * (i - t[l][r]));
}
}
printf ("%d\n", ans);
return 0;
}
T4
小氢锌 dp。
网格图 dp 求路径方案数这种东西一般都是神秘 dp,你发现有限制点 \((i,p_i)\) 不让走。对于固定排列可以考虑容斥做,即钦定经过 \(i\) 个点,经过限制点的答案都乘 \(-1\)。
排列不定考虑如何做,记 \(p_i = -1\) 为不定点,记不定点个数为 \(cnt\),记 \(f_{i,j,k,0/1,0/1}\) 表示走到 \((i,j)\),已经确定了 \(k\) 个不定点经过了,该行是否有限制点,该列是否有限制点的贡献。
初始化有 \(f_{0,0,0,1,1} = 1\),\(f_{i,j,k,x,y} \to \begin{cases} f_{i+1,j,k,p,q} \\ f_{i,j+1,k,p,q}\end{cases}\)。对于不定点如果该列或该行没有限制点就将其放在该行或该列,然后减去 \(f_{i,j,k,x,y}\),原本是限制点的点也同理。
最后统计答案要乘系数 \((cnt-k)!\),因为后面不定点可任意排。
#include <bits/stdc++.h>
#define int long long
using namespace std;
constexpr int N = 210, Mod = 998244353;
int n, a[N], f[N][N][N][2][2], b[N], cnt, fac[N];
void addon (int &x, int y) {
x = (x + y >= Mod) ? x + y - Mod : x + y;
}
void subon (int &x, int y) {
x = (x - y < 0) ? x - y + Mod : x - y;
}
signed main() {
scanf ("%lld", &n);
for (int i = 1; i <= n; i ++)
scanf ("%lld", &a[i]), b[i] = -1;
for (int i = 1; i <= n; i ++) {
if (a[i] != -1) {
b[a[i]] = i;
}
else {
cnt++;
}
}
fac[0] = 1;
for (int i = 1; i <= cnt; i ++)
fac[i] = fac[i - 1] * i % Mod;
// memset (f, ~0x3f, sizeof(f));
f[0][0][0][1][1] = 1;
for (int i = 0; i <= n + 1; i ++) {
for (int j = 0; j <= n + 1; j ++) {
for (int k = 0; k <= cnt; k ++) {
for (int x = 0; x < 2; x ++) {
for (int y = 0; y < 2; y ++) {
if (f[i][j][k][x][y]) {
int val = f[i][j][k][x][y];
if (i < n + 1) {
int p = (a[i + 1] >= 0) ? 1 : 0;
addon(f[i + 1][j][k][p][y], val);
if (a[i + 1] == j && j)
subon(f[i + 1][j][k][1][1], val);
if (a[i + 1] == -1 && !y)
subon(f[i + 1][j][k + 1][1][1], val);
}
if (j < n + 1) {
int q = (b[j + 1] >= 0) ? 1 : 0;
addon(f[i][j + 1][k][x][q], val);
if (b[j + 1] == i && i)
subon(f[i][j + 1][k][1][1], val);
if (b[j + 1] == -1 && !x)
subon(f[i][j + 1][k + 1][1][1], val);
}
}
}
}
}
}
}
int ans = 0;
for (int i = 0; i <= cnt; i ++)
addon(ans, fac[cnt - i] * f[n + 1][n + 1][i][1][1] % Mod);
printf ("%lld\n", ans);
return 0;
}

浙公网安备 33010602011771号