「赛后总结」高考集训:NOIP 模拟测试 A2

「赛后总结」高考集训:NOIP 模拟测试 A2

点击查看目录

离暑假挺近的,于是放到一个合集里。

你 A1 搁哪呢

学长的馈赠 5,打过。


昨天推的歌纯属魔怔,我也不知道推的时候是一个什么样的精神状态。

今日推歌

image

Идеи Rolling_Star.


jijidawang 的终端内:

killall -9 K8He

jijidawang:根本杀不掉啊(指进程(\(\leftarrow\)存疑))。

题解

T1 南

原:P4550 收集邮票

人类智慧题。

Rolling_star 光速长茄,好强啊!

好久不写概率期望,完全不会了。

思路

\(f_i\) 表示已经取完 \(i\) 种武器,要取到 \(n\) 种武器的期望步数,显然 \(f_n = 0\)

\[\begin{aligned} f_{i} &= \frac{i}{n}(f_{i} + 1) + \frac{n - i}{n}(f_{i + 1} + 1)\\ f_{i} &= \frac{i}{n}f_{i} + \frac{i}{n} + \frac{n - i}{n}f_{i + 1} + \frac{n - i}{n}\\ \frac{n - i}{n}f_{i} &= \frac{n - i}{n}f_{i + 1} + 1\\ f_{i} &= f_{i + 1} + \frac{n}{n - i}\\ \end{aligned} \]

\(g_i\) 表示已经取完 \(i\) 种武器,要取到 \(n\) 种武器的期望钱数,显然 \(g_n = 0\)

\[\begin{aligned} g_{i} &= \frac{i}{n}(g_{i} + f_{i} + 1) + \frac{n - i}{n}(g_{i + 1} + f_{i + 1} + 1)\\ g_{i} &= \frac{i}{n}g_{i} + \frac{i}{n}f_{i} + \frac{i}{n} + \frac{n - i}{n}g_{i + 1} + \frac{n - i}{n}f_{i + 1} + \frac{n - i}{n}\\ \frac{n - i}{n}g_{i} &= \frac{i}{n}f_{i} + \frac{n - i}{n}g_{i + 1} + \frac{n - i}{n}f_{i + 1} + 1\\ g_{i} &= \frac{i}{n - i}f_{i} + g_{i + 1} + f_{i + 1} + \frac{n}{n - i}\\ \end{aligned} \]

直接逆推到 \(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 昌

原:Serval and Rooted Tree

思路

犹豫了一下要不要讲我这个与众不同的 DP,然后就散场了,呃呃。那就这里讲一下罢。

这个「考虑局部排名」的 trick 好像是去年暑假模拟赛学到的。

\(f_{u}\) 表示 \(u\) 节点的值在其子树的叶子节点中的排名,显然 \(f_1\) 即为答案。\(sz_u\) 表示以 \(u\) 为根的子树中有几个叶子节点。转移:

\[f_u = \begin{cases} 1 &sz_u = 1\\ sz_u - \min_{v\text{ is }u\text{'s son}}\{sz_v - f_v\} &sz_u\ne1\text{ and } a_u = 1\\ sz_u - (\sum_{v\text{ is }u\text{'s son}}sz_v - f_v + 1) + 1 &\text{otherwise}\\ \end{cases} \]

代码

点击查看代码
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\) 的空间。转移用容斥:

\[f_{i, j} = f_{i - 1, j} + f_{i, j - i} - f_{i - 1, j - i \times(i + 1)} \]

\(g_{i, j}\) 表示拿了 \(i\)\(\sqrt{n} + 1 \sim n\) 中的物品,占了 \(j\) 的空间。然后开始人类智慧。

对于这个状态我们有两个操作:

  • 往里面新放一个第 \(\sqrt{n} + 1\) 个物品。
  • 所有物品编号加 \(1\)

于是有了很方便的转移:

\[\begin{aligned} g_{i, j + i} & \leftarrow g_{i, j + i} + g_{i, j}\\ g_{i + 1, j + \sqrt{n} + 1} & \leftarrow g_{i + 1, j + \sqrt{n} + 1} + g_{i, j}\\ \end{aligned} \]

方案数乘起来。

代码

点击查看代码
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;
	}
}
posted @ 2023-06-12 22:13  K8He  阅读(84)  评论(5编辑  收藏  举报