OIFC 2025.11.7 模拟赛总结

寄,\(0 + 40 + 5 + 0 = 45pts\),可以退役了。

T1

错因:在写 dfs 求解答案构造的时候,把判断已访问点数的语句写在了 dfs 最前面。这样会导致调用 dfs(代码中的 runk 函数)但有概率不访问任何节点,然后就输出了一些从 \(u\) 走到 \(u\) 的边。

分数:\(\color{#00AD07}100 \color{#000000}\rightarrow \color{#EE4135}0\) 😦

题目描述

这次她来到了一个全新的国家。这个国家的地图上有 \(n\) 个景点,景点之间由 \(n - 1\) 条双向公路连接,并且任意两个景点之间总是可以互相到达。

小 L 的旅游时间只有 \(m\) 天,每天她都会选择沿着公路走向一个与当前景点相邻的景点。特别地,由于第一天小 L 是坐飞机抵达的,因此第一天的景点可以任意选择。

小 L 认为:没有规矩,不成方圆。因此,她早早定下了自己的旅游计划:游览恰好 \(k\) 个不同的景点。她希望你能帮她规划一条满足要求的路线,或者告诉她无解。帮帮小 L 吧!

对于所有数据,保证 \(2 \leq \sum n \leq 4 \times 10^5\) 并且 \(2 \leq k \leq n \leq 10^5\)\(1 \leq m \leq 2n\)

题解

先考虑 \(k = n\) 的情况。

注意到如果要求返回起点,答案一定是 \(2n - 1\)

证明很简单,由于每条边 \(e\) 都会将点集划分成 \(S,T\) 两个部分,从 \(S \rightarrow T\)\(T \rightarrow S\) 都至少要经过 \(e\) 一次,因此一条边会至少被经过两次。

另一方面,显然做一遍 dfs 就是合法构造。

那么再考虑不用返回起点怎么做。显然,对于起点 \(s\) 和终点 \(t\),答案为 \(2n - 1 - dis(s,t)\)。而 \(dis(s,t)\) 最大时,\(s\)\(t\) 就是这棵树的直径的两个端点!因此我们以直径的一个端点为根构建新的树,然后去跑 dfs 即可。注意保证终点必须是直径的另一个端点

理解 \(k = n\) 后,\(k \not= n\) 就简单了。走过的点一定是大小 \(k\) 的联通块,所以我们可以修改一下 \(k = n\) 的算法。在搜索非直径的子树时,我们记录一个变量 \(vised\) 表示搜索了几个节点。每次搜索新的节点之前保证 \(vised < k - len_{直径}\) 即可。

注意,不要出现 \(u \rightarrow u\) 这样的边!!!

注意,不要出现 \(u \rightarrow u\) 这样的边!!!

注意,不要出现 \(u \rightarrow u\) 这样的边!!!

参考代码(C++14):

#include<bits/stdc++.h>
using namespace std;
inline int read(){
	int x = 0, f = 1;
	char ch = getchar();
	while(!isdigit(ch)){
		if(ch == '-') f = -1;
		ch = getchar();
	}
	while(isdigit(ch)){
		x = (x << 1) + (x << 3) + (ch ^ 48);
		ch = getchar();
	}
	return x * f;
}
inline void write(int x){
	if(x < 0) putchar('-'), x = -x;
	if(x > 9) write(x / 10);
	putchar(x % 10 + '0');
	return;
}

int n, m, k, dep[400005], mxdep[400005], sz[400005], fat[400005];
vector<int> e[400005], path;

inline void dfs(int u, int fa){
	dep[u] = mxdep[u] = dep[fa] + 1, sz[u] = 1, fat[u] = fa;
	for(auto v : e[u]) if(v != fa){
		dfs(v, u);
		sz[u] += sz[v];
		mxdep[u] = max(mxdep[u], mxdep[v]);
	}
	return;
}

int vised = 0;
inline void runk(int u, int lim){
	path.push_back(u), vised++;
	for(auto v : e[u]) if(v != fat[u]){
		if(vised < lim){
			runk(v, lim);
			path.push_back(u);
		}
	}
	return;
}

inline void run(int u, int fa, int need){
	if(need <= 0) return;
	path.push_back(u), need--;
	int MaxDeltaDep = -1, nice = -1;
	for(auto v : e[u]) if(v != fa){
		if(mxdep[v] - dep[u] > MaxDeltaDep){
			MaxDeltaDep = mxdep[v] - dep[u];
			nice = v;
		}
	}
	vised = 0;
	for(auto v : e[u]) if(v != fa){
		if(v == nice) continue;
		if(vised < need - MaxDeltaDep){
			runk(v, need - MaxDeltaDep);
			path.push_back(u);
		}
	}
	run(nice, u, need - vised);
	return;
}

inline void Solve(){
	for(int i = 1; i <= n; i++) e[i].clear();
	n = read(), m = read(), k = read();
	for(int i = 1; i < n; i++){
		int u = read(), v = read();
		e[u].push_back(v), e[v].push_back(u);
	}

	if(m < k){
		puts("No");
		return;
	}

	dfs(1, 0);
	int mdep = -1, root;
	for(int i = 1; i <= n; i++){
		if(dep[i] > mdep){
			mdep = dep[i];
			root = i;
		}
	}
	dfs(root, 0);
	
	path.clear(), run(root, 0, k);
	while((int)path.size() < m) path.push_back(path[path.size() - 2]);
	
	if((int)path.size() > m) puts("No");
	else{
		puts("Yes");
		for(auto p : path) write(p), putchar(' ');
		putchar('\n');
	}
	return;
}
signed main(){
	freopen("walk.in", "r", stdin);
	freopen("walk.out", "w", stdout);
	int T = read();
	while(T--) Solve();
	return 0;
}

T2

唯一没挂分的一道。

分数:\(\color{Orange}40\)

题目描述

区国有一位魔法师小 X。小 X 十分擅长魔法。

区国有一位模数师小 Z。小 Z 十分擅长模数。

楚国要攻打区国了,区太祖准备让小 X 和小 Z 展示他们的才能。

具体来说,区国边界有 \(n\) 个城市。小 X 有两个 \(1 \sim n\) 的排列 \(a_i,b_i\)。小 Z 会对于每个 \(i\) 选择 \(c_i = a_i\)\(c_i = b_i\),之后小 X 将作法并给第 \(i\) 个城市添加一个种类为 \(c_i\) 的防御魔法。

楚国的法师虽然水平并不是很强大,但是记忆力很好,在这一点上远胜小 X。所以如果所有城市的防御魔法的不同种类数小于 \(k\),楚国的法师就能够轻松击破所有的防御魔法。

小 Z 作为模数师,自然就会好奇如下问题:他有多少种不同的选择方法,使得最终的 \(c_i\) 有至少 \(k\) 种不同的数?答案对 \(10^9 + 7\) 取模。

题解

posted @ 2025-11-07 16:00  zhang_kevin  阅读(1)  评论(0)    收藏  举报