「赛后总结」高考集训:NOIP 模拟测试 A2
「赛后总结」高考集训:NOIP 模拟测试 A2
离暑假挺近的,于是放到一个合集里。
你 A1 搁哪呢
学长的馈赠 5,打过。
昨天推的歌纯属魔怔,我也不知道推的时候是一个什么样的精神状态。
今日推歌
Идеи Rolling_Star.
jijidawang 的终端内:
killall -9 K8He
jijidawang:根本杀不掉啊(指进程(\(\leftarrow\)存疑))。
题解
T1 南
人类智慧题。
Rolling_star 光速长茄,好强啊!
好久不写概率期望,完全不会了。
思路
设 \(f_i\) 表示已经取完 \(i\) 种武器,要取到 \(n\) 种武器的期望步数,显然 \(f_n = 0\)。
设 \(g_i\) 表示已经取完 \(i\) 种武器,要取到 \(n\) 种武器的期望钱数,显然 \(g_n = 0\)。
直接逆推到 \(g_0\)。
代码
点击查看代码
const ll N = 1e5 + 10;
namespace SOLVE {
ll n, tag; ldb f[N], g[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 void In () {
n = rnt ();
return;
}
inline void Solve () {
f[n] = g[n] = 0.0;
for_ (i, n - 1, 0) {
f[i] = f[i + 1] + (ldb)(n) / (ldb)(n - i);
g[i] = (ldb)(i) / (ldb)(n - i) * f[i] + g[i + 1] + f[i + 1] + (ldb)(n) / (ldb)(n - i);
}
return;
}
inline void Out () {
printf ("%.2Lf\n", g[0]);
return;
}
}
T2 昌
思路
犹豫了一下要不要讲我这个与众不同的 DP,然后就散场了,呃呃。那就这里讲一下罢。
这个「考虑局部排名」的 trick 好像是去年暑假模拟赛学到的。
设 \(f_{u}\) 表示 \(u\) 节点的值在其子树的叶子节点中的排名,显然 \(f_1\) 即为答案。\(sz_u\) 表示以 \(u\) 为根的子树中有几个叶子节点。转移:
代码
点击查看代码
const ll N = 3e5 + 10;
namespace SOLVE {
ll n, a[N], sz[N], f[N], ans;
std::vector <ll> tu[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 Dfs (ll u) {
ll qwq = (a[u] == 1) ? N : 0;
far (v, tu[u]) {
Dfs (v), sz[u] += sz[v];
if (a[u] == 1) qwq = std::min (qwq, sz[v] - f[v]);
else qwq += sz[v] - f[v] + 1;
}
if (!sz[u]) sz[u] = f[u] = 1;
else if (a[u] == 1) f[u] = sz[u] - qwq;
else f[u] = sz[u] - qwq + 1;
return;
}
inline void In () {
n = rnt ();
_for (i, 1, n) a[i] = rnt ();
_for (i, 2, n) {
ll fa = rnt ();
tu[fa].push_back (i);
}
return;
}
inline void Solve () {
Dfs (1);
return;
}
inline void Out () {
printf ("%lld\n", f[1]);
return;
}
}
T3 起
思路
非正解,但飞快。
卡了半天常卡到 2881 ms,以为最优解了结果被 Really 学姐刚提交的 2451 ms 爆了 😅
根据数学直觉,满足条件矩形不多,于是把所有满足条件的矩形塞进一个 vector
。但是事实上极限下 \(\frac{500\times500}{4} = 62500\) 挺大的,过不去。
考虑一种暴力,前缀和处理出来一个矩形内是否有一个点可以作为一个大小为 \(2k\times2k\) 的矩形左上角,是稳的 \(O(n^3 + nq)\),会 T,因为试过。
大眼观察,发现第一种做法处理比较大的矩形快,第二种做法处理比较小的矩形快,于是设一个值 \(k\),规模小于等于 \(2k\times2k\) 用第二种,否则第一种。实测 \(k\) 等于 \(5\) 最快。
这种「把处理小范围和处理大范围的两种算法合起来」的方法是从 ABC 293 F 学到的,果然板刷 Atcoder 上的题有好处。
upd 2024/03/15 : 同学也打这场模拟赛了才发现我写过这场题解呃呃,现在知道这玩意叫阈值分治了 .
代码
点击查看代码
const ll N = 510, K = 5;
namespace SOLVE {
ll n, m, a[N][N], q, co[N][N][4], b[K + 1][N][N], ans; char s[N][N];
class SQ { public: ll i1, j1, i2, j2; };
std::vector <SQ> p[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 ll Color (char c) {
if (c == 'B') return 0;
else if (c == 'W') return 1;
else if (c == 'P') return 2;
else return 3;
}
inline bool Check1 (ll i1, ll j1, ll i2, ll j2, ll c) {
ll sum = co[i2][j2][c] - co[i1 - 1][j2][c] - co[i2][j1 - 1][c] + co[i1 - 1][j1 - 1][c];
return (bool)(sum == (i2 - i1 + 1) * (j2 - j1 + 1));
}
inline ll Check2 (ll i1, ll j1, ll i2, ll j2, ll k) {
return b[k][i2][j2] - b[k][i1 - 1][j2] - b[k][i2][j1 - 1] + b[k][i1 - 1][j1 - 1];
}
inline void In () {
n = rnt (), m = rnt (), q = rnt ();
_for (i, 1, n) scanf ("%s", s[i] + 1);
return;
}
inline void Solve () {
_for (i, 1, n) {
_for (j, 1, m) {
a[i][j] = Color (s[i][j]);
_for (k, 0, 3) {
co[i][j][k] = co[i - 1][j][k] + co[i][j - 1][k] - co[i - 1][j - 1][k];
if (k == a[i][j]) ++co[i][j][k];
}
}
}
_for (i, 1, n) {
_for (j, 1, m) {
_for (k, 1, std::min (n - i + 1, m - j + 1) / 2) {
if (k <= K) b[k][i][j] = b[k][i - 1][j] + b[k][i][j - 1] - b[k][i - 1][j - 1];
if (!Check1 (i, j, i + k - 1, j + k - 1, 0)) continue;
if (!Check1 (i, j + k, i + k - 1, j + 2 * k - 1, 1)) continue;
if (!Check1 (i + k, j, i + 2 * k - 1, j + k - 1, 2)) continue;
if (!Check1 (i + k, j + k, i + 2 * k - 1, j + 2 * k - 1, 3)) continue;
if (k > K) p[k].push_back ((SQ){i, j, i + 2 * k - 1, j + 2 * k - 1});
else ++b[k][i][j];
}
}
}
return;
}
inline void Out () {
_for (i, 1, q) {
ll x1 = rnt (), y1 = rnt (), x2 = rnt (), y2 = rnt ();
ll ans = 0;
for_ (j, n / 2, K + 1) {
if (p[j].empty ()) continue;
far (tmp, p[j]){
if (tmp.i1 < x1) continue;
if (tmp.j1 < y1) continue;
if (tmp.i2 > x2) continue;
if (tmp.j2 > y2) continue;
ans = j;
}
if (ans) break;
}
for_ (k, K, 1) {
if (ans) break;
ll p1 = x2 - 2 * k + 1, p2 = y2 - 2 * k + 1;
if (p1 < x1 || p2 < y1) continue;
if (Check2 (x1, y1, p1, p2, k)) ans = k;
}
printf ("%lld\n", ans * ans * 4);
}
return;
}
}
T4 义
思路
这个非质数模数搞得我以为要从这里入手。
犹豫了一下讲不讲为啥减去 \(f_{i - 1, j - i(i + 1)}\),然后被 Rolling_star 讲了,呃呃。
对前 \(\sqrt{n}\) 种物品和第 \(\sqrt{n} + 1 \sim n\) 个物体分别 DP。
设 \(f_{i, j}\) 表示取完第 \(i\) 种物体,占了 \(j\) 的空间。转移用容斥:
设 \(g_{i, j}\) 表示拿了 \(i\) 件 \(\sqrt{n} + 1 \sim n\) 中的物品,占了 \(j\) 的空间。然后开始人类智慧。
对于这个状态我们有两个操作:
- 往里面新放一个第 \(\sqrt{n} + 1\) 个物品。
- 所有物品编号加 \(1\)。
于是有了很方便的转移:
方案数乘起来。
代码
点击查看代码
const ll N = 1e5 + 10, SN = 320, P = 23333333;
namespace SOLVE {
ll n, sn, f[N], g[SN][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 void In () {
n = rnt ();
sn = sqrt (n);
return;
}
inline void Solve () {
g[0][0] = f[0] = 1;
_for (i, 1, sn) {
_for (j, i, n) f[j] = (f[j] + f[j - i]) % P;
for_ (j, n, i * (i + 1)) f[j] = (f[j] - f[j - i * (i + 1)] + P) % P;
}
_for (i, 0, sn) {
_for (j, 0, n) {
if (j + i <= n && i) g[i][j + i] = (g[i][j + i] + g[i][j]) % P;
if (j + sn + 1 <= n) g[i + 1][j + sn + 1] = (g[i + 1][j + sn + 1] + g[i][j]) % P;
}
}
_for (i, 0, n) {
ll b = 0;
_for (j, 0, sn) b = (b + g[j][n - i]) % P;
ans = (ans + f[i] * b % P) % P;
}
return;
}
inline void Out () {
printf ("%lld\n", ans);
return;
}
}