NOIP 备赛:CF 2E 板刷

\(2024.11.05\) 之前的比赛排着刷。

CF2028 E

这道题主要考察的是手玩能力和转移技巧。
给定一棵树,根为 \(1\)。爱丽丝的起点位于某个顶点 \(v\) 。她想走出洞口,但不幸的是,红心皇后已经下令处死她。

每分钟都会掷一枚公平的硬币。如果硬币是正面,爱丽丝就可以移动到她当前位置的相邻顶点,反之,红心皇后就可以把爱丽丝拉到皇后选择的相邻顶点。如果爱丽丝最终出现在树的非根叶子上,那么爱丽丝就输了。

假设两人都以最佳方式移动,计算爱丽丝成功逃离每个起始顶点 \(1\le v\le n\) 的概率。对 \(998\,244\,353\) 取模的值。\(n \le 10 ^ 5\)


首先需要一个 key observation,就是 Alice 的最优策略一定是向父亲走,红心皇后的最优策略一定是向深度最小的叶子走。设每个点深度最小的儿子是 \(s_u\),Alice 从这个点出发逃离概率是 \(f_u\)。那么有转移:

\[f(u) = \dfrac{1}{2}(f(s_u) +f(fa_u)) \]

其中 \(f(1) = 1, f(u \mid u \in \text{leaf}) = 0\)

这个转移方式有点技巧。假设有链 \(1 \rightarrow 2 \rightarrow 3 \rightarrow 4 \cdots\)。那么 \(f(2) = \dfrac{1}{2} f(1) + \dfrac{1}{2} f(3)\)

接下来有 \(f(3) = \dfrac{1}{2} f(2) + \dfrac{1}{2} f(4) = \dfrac{1}{4} f(1) + \dfrac{1}{4} f(3) + \dfrac{1}{2} f(4)\)。移项整理得到 \(f(3) = \dfrac{1}{3} f(1) +\dfrac{2}{3} f(4)\)

同样操作可以发现规律 \(f(i) = \dfrac{i}{1} f(1) + \dfrac{i - 1}{i} f(i + 1)\)

首先将树按照浅儿子剖成若干链,对链单独转移,然后再对链顶单独转移即可。预处理逆元可以做到线性。

int f[N], n, m, fa[N], dep[N], s[N]; 
vector<int> E[N];
void dfs(int u, int F) {
	fa[u] = F; for (auto v : E[u]) if (v ^ F) {
		dfs(v, u); if (!dep[u]) dep[u] = dep[v] + 1, s[u] = v;
		else if (dep[v] + 1 < dep[u]) s[u] = v, dep[u] = dep[v] + 1;
	}
}
void dp(int u, int t, int d) {
	if (!s[u]) return; dp(s[u], t, d + 1); 
	f[u] = (d * qpow(d + 1) % mod * f[s[u]] % mod + qpow(d + 1) * t % mod) % mod;
	for (auto v : E[u]) if ((v ^ fa[u]) and (v ^ s[u])) dp(v, f[u] % mod, 1);
}
void sub() {
	read(n);
	rep(i, 1, n) E[i].clear(), dep[i] = 0, s[i] = 0, f[i] = 0;
	rop(i, 1, n) {
		int a, b; read(a, b);
		E[a].push_back(b);
		E[b].push_back(a);
	} dfs(1, 0); f[1] = 1; dp(1, 1, 0);
	rep(i, 1, n) printf("%lld ", f[i]);
	return;
}

CF2032 E

这道题考察的主要是观察力和构造水平。

给你一个数组 \(a\),可以进行若干次操作,可以进行任意次操作。

每次操作选择一个位置 \(i(1 \le i \le n)\),并且使 \(a_i \leftarrow a_i + 2\)\(a_{i - 1} \leftarrow a_{i - 1} + 1\)\(a_{i + 1} \leftarrow a_{i + 1} + 1\)。特别地,\(1\) 的上一个位置是 \(n\)\(n\) 的下一个位置是 \(1\),也就是说,这是一个循环数组。你需要构造一组方案。


首先我们发现这个答案有一定的单调性。即如果能使他们都变成 \(x\),那么也可以通过把他们全操作一遍使他们变成 \(x + 4\)。因此先设他们能变成一个大数 \(I\),将问题转化为判定性问题。

不妨设 \(b_i = I - a_i\),则条件充要转化为:\(\forall i \le n\)\(p_{i - 1} + 2 p_i + p_{i + 1} = b_i\)

发现将每个位置作为开头,剩下的往后顺延,将奇数位置的 \(b\) 的和,减去偶数位置的 \(b\) 的和,得到的就是 \((p_i + p_{i - 1}) \times 2\)。有了这个之后,剩下的就好求了。复杂度是线性的。

void sub() {
	read(n);
	rep(i, 1, n) read(a[i]);
	auto solve = [&](int q) -> bool {
		rep(i, 1, n) b[i] = q - a[i];
		s[1] = b[1], s[2] = b[2];
		rep(i, 3, n) s[i] = s[i - 2] + b[i];
		rep(i, 1, n) {
			t[i] = s[(i & 1) ? n : n - 1] - s[max(i - 2, 0)];
			t[i] += s[i - 1];
			t[i] -= s[(i & 1) ? n - 1 : n] - s[max(i - 1, 0)];
			t[i] -= s[max(i - 2, 0)];
		}
		rep(i, 1, n) if (t[i] & 1) return 0; rep(i, 1, n) t[i] >>= 1;
		int o = 0; rep(i, 2, n) o += ((i & 1) ? -1 : 1) * t[i]; // p[1] - p[n]
		if ((o + t[1]) & 1) return 0;
		p[1] = (o + t[1]) >> 1;
		rep(i, 2, n) p[i] = t[i] - p[i - 1];
		rep(i, 1, n) if (p[i] < 0) return 0;
		rep(i, 1, n) printf("%lld ", p[i]); puts("");
		return 1;
	};
	if (solve(I)) return;
	if (solve(I + 1)) return;
	if (solve(I + 2)) return;
	if (solve(I + 3)) return;
}
posted @ 2024-11-15 19:19  Link-Cut-Y  阅读(229)  评论(0)    收藏  举报