「赛后总结」20230724 CSP 模拟赛
点击查看目录
目录
想听歌,想看巨人,但是没有条件。
总结。
rk1 三个首杀,前二没有 HZOI 土著,前三没有 HZOI 2022 人,咋整的呀?
T1 5min 过掉样例交了一发,然后手玩一个样例不小心 Hack 掉了,改完了手玩一个样例又 Hack 掉了,最后对拍观察数据猜出正解,可见对拍的重要性。
T2 其实是有实力过的,赛时已经想到了 \(n^2\) 写法及优化,但是一直以为 \(n^2\) 暴力写假了就没时间去优化了,这种错觉的产生原因是搜索部分的暴力因为哈希冲突挂了,大概最后 20min 才发现。
T3 假二分能 65 分,据 xuany 说加特判 78 分,搬题人能不能用点心。
T4 看出来是状压但不会压。
这就是睡觉的重要性,实测多睡两个小时可以前进 11 名。
题解
T1 最长上升子序列 ARC125C
思路
sto Chen_jr orz
代码
点击查看代码
const ll N = 2e5 + 10;
namespace SOLVE {
ll n, k, m, a[N], vis[N], ans[N];
inline ll rnt () {
ll x = 0, w = 1; char c = getchar ();
while (!isdigit (c)) { if (c == '-') w = -1; c = getchar (); }
while (isdigit (c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar ();
return x * w;
}
inline void In () {
n = rnt (), k = rnt (), m = 0;
_for (i, 1, k) vis[a[i] = rnt ()] = 1;
return;
}
inline void Solve () {
ll j = 1;
_for (i, 1, k - 1) {
ans[++m] = a[i];
while (vis[j]) ++j;
if (j > a[i]) continue;
ans[++m] = j, ++j;
}
for_ (i, n, a[k] + 1) if (!vis[i]) ans[++m] = i;
ans[++m] = a[k];
for_ (i, a[k] - 1, j) if (!vis[i]) ans[++m] = i;
return;
}
inline void Out () {
_for (i, 1, n) printf ("%lld ", ans[i]);
puts ("");
return;
}
}
T2 独特序列 ARC125D
思路
设 \(f_i\) 表示以 \(i\) 结尾有多少个合法序列,有转移:
\[f_i = \sum_{j = 1}^{i - 1}(\prod_{k = j + 1}^{i - 1}[a_k\ne a_i][a_k\ne a_j])f_j \]答案为:
\[\sum_{i = 1}^{n}(\prod_{k = i + 1}^{n}[a_k\ne a_i])f_i \]预处理可做到 \(O(n^2)\)。
不难发现对于 \(j < i\),当且仅当 \(a_j\) 这个数在 \([1, i - 1]\) 中是最后一次出现时且 \((j, i)\) 中不存在 \(a_i\) 这个数时才会转移到 \(f_i\),那我们把没用的及时清零,使用 BIT 维护。
代码
点击查看代码
const ll N = 2e5 + 10, P = 998244353;
namespace BIT {
class BIT {
public:
ll n;
private:
ll b[N];
inline ll lowbit (ll x) { return x & -x; }
public:
inline void Update (ll x, ll y){
if (x <= 0) return;
while (x <= n) b[x] = (b[x] + y + P) % P, x += lowbit (x);
return;
}
inline ll Query (ll x){
ll sum = 0;
while (x > 0) sum = (sum + b[x]) % P, x -= lowbit (x);
return sum;
}
};
}
namespace SOLVE {
ll n, a[N], la[N], f[N], vis[N], ans;
BIT::BIT tr;
inline ll rnt () {
ll x = 0, w = 1; char c = getchar ();
while (!isdigit (c)) { if (c == '-') w = -1; c = getchar (); }
while (isdigit (c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar ();
return x * w;
}
inline void In () {
tr.n = n = rnt ();
_for (i, 1, n) {
a[i] = rnt ();
la[i] = vis[a[i]], vis[a[i]] = i;
}
return;
}
inline void Solve () {
_for (i, 1, n) {
f[i] = (!la[i] + tr.Query (i - 1) - tr.Query (la[i] - 1) + P) % P;
tr.Update (la[i], -f[la[i]]), f[la[i]] = 0, tr.Update (i, f[i]);
}
ans = tr.Query (n);
return;
}
inline void Out () {
printf ("%lld\n", ans);
return;
}
}
T3 最大 GCD ARC126C
思路
这玩意没有单调性,不能二分。
首先考虑能不能全加到最大值,能的话全加上去,还有剩余就平均分。
这个值域很小,那么枚举最大公约数,考虑如何快速判断解是否可行。即解不等式:
\[\begin{aligned} \sum_{i = 1}^{n}\left\lceil\frac{a_i}{x}\right\rceil - a_i &\le k\\ \sum_{i = 1}^{n}\left\lceil\frac{a_i}{x}\right\rceil &\le k + \sum_{i = 1}^{n}a_i \end{aligned} \]考虑快速求左边这玩意,发现取值只有 \(\left\lceil\frac{\max_{i = 1}^{n}a_i}{x}\right\rceil\) 中,开个桶然后前缀和记录。
代码
点击查看代码
const ll N = 1e6 + 10;
namespace SOLVE {
ll n, k, sum, mx, mn, a[N], t[N], ans;
inline ll rnt () {
ll x = 0, w = 1; char c = getchar ();
while (!isdigit (c)) { if (c == '-') w = -1; c = getchar (); }
while (isdigit (c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar ();
return x * w;
}
inline bool Check (ll x) {
ll q = -sum;
_for (i, 1, (mx - 1) / x + 1) q += (t[i * x] - t[(i - 1) * x]) * i * x;
return q <= k;
}
inline void In () {
n = rnt (), k = rnt ();
_for (i, 1, n) {
a[i] = rnt ();
++t[a[i]], sum += a[i];
mx = std::max (mx, a[i]);
mn = std::min (mn, a[i]);
}
return;
}
inline void Solve () {
_for (i, 1, mx * 2) t[i] += t[i - 1];
if (mx * n - sum <= k) {
ans = mx + (k - mx * n + sum) / n;
return;
}
for_ (i, mx, 1) if (Check (i)) { ans = i; break; }
return;
}
inline void Out () {
printf ("%lld\n", ans);
return;
}
}
T4 连续子段 ARC126D
思路
考虑状压。设 \(f_{i, j}\) 表示当前到了第 \(i\) 个数,当前排好的数的情况为 \(j\),考虑转移。
如果把 \(a_i\) 排进来,它肯定是从后往前移,次数为当前排好的数中比 \(a_i\) 大的数的数量。
不弄进来的话,当前序列还需要统一向右挪一次,或者让以后排进来的数多向左挪一次。
为方便直接设 \(s(x)\) 表示 \(\operatorname{popcount}(x)\),转移方程:
\[f_{i, j} = \min\{f_{i - 1, j\oplus 2^{a_i - 1}} + s(\left\lfloor\frac{j}{2^{a_i}}\right\rfloor), f_{i - 1, j} + s(j), f_{i - 1, j} + s(2^k - 1) - s(j)\} \]可以滚一维。
代码
点击查看代码
const ll N = 210, S = 1 << 16, inf = 1ll << 40;
namespace SOLVE {
ll n, k, a[N], f[S], ans;
inline ll rnt () {
ll x = 0, w = 1; char c = getchar ();
while (!isdigit (c)) { if (c == '-') w = -1; c = getchar (); }
while (isdigit (c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar ();
return x * w;
}
inline ll Count (ll x) { ll s = 0; while (x) s += x & 1, x >>= 1; return s; }
inline void In () {
n = rnt (), k = rnt ();
_for (i, 1, n) a[i] = rnt ();
return;
}
inline void Solve () {
memset (f, 0x3f, sizeof (f));
f[0] = 0;
_for (i, 1, n) {
ll x = a[i];
for_ (j, (1 << k) - 1, 0) {
if (f[j] >= inf) continue;
if (!((1 << (x - 1)) & j))
f[j | (1 << (x - 1))] = std::min (f[j | (1 << (x - 1))], f[j] + __builtin_popcount (j >> (x - 1)));
f[j] += std::min (__builtin_popcount (j), (int)(k) - __builtin_popcount (j));
}
}
return;
}
inline void Out () {
printf ("%lld\n", f[(1 << k) - 1]);
return;
}
}