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\) 取模。

题解

T3

T4 树

呜呜构造错了。

题目描述

给定一个数 \(n \geq 3\),你需要构造一颗树,其中每个点有正整数点权,满足:

  • 对于任意一个 \(k \in [1,n]\),满足存在一条点数 \(>1\) 的链,使得链上点的 \(gcd = k\)

由于一些原因,你希望树的点数与权值尽可能小。具体来说,每个点有一个限制 \(X,Y\),你需要保证你构造的树的点数 \(\leq X\),且你构造的树的点权 \(\max \leq Y\),你通过这个点。

对于所有测试点,满足 \(Y = 11000\)

本题共 \(20\) 个测试点,测试点等分,满足的限制如下:

测试点编号 \(n\) \(X=\)
\(1\) \(=20\) \(20\)
\(2\) \(=2500\) \(5000\)
\(3,4\) \(\leq 50\) \(n\)
\(5 \sim 7\) \(\leq 400\) \(n\)
\(8\) \(=800\) \(1200\)
\(9\) \(=1600\) \(2400\)
\(10\) \(=2500\) \(3750\)
\(11\) \(=800\) \(900\)
\(12\) \(=1600\) \(1700\)
\(13\) \(=2000\) \(2100\)
\(14\) \(=2500\) \(2600\)
\(15\) \(\leq 800\) \(n + 1\)
\(16\) \(\leq 1600\) \(n + 1\)
\(17\) \(\leq 2000\) \(n + 1\)
\(18\) \(\leq 2500\) \(n + 1\)
\(19\) \(\leq 2000\) \(n\)
\(20\) \(\leq 2500\) \(n\)

题解

考虑非乱搞的构造算法。

对于所有 \(a\) 满足 \(\gcd(6, a) = 1\),我们通过 \(2\)\(3\) 两个数构造。把所有形如 \(a \times 2^x3^y\) 的数,写成一个杨表:

\[\begin{matrix} a & 2a & 4a & 8a & 16a & 32a & \color{red}64a\\ 3a & 6a & 12a & 24a & \color{red}48a & \color{red}96a \\ 9a & 18a & \color{red}36a & \color{red}72a \\ 27a & \color{red}54a \\ \color{red}81a \end{matrix} \tag{1} \]

P.S. 这个表的 Markdown 源码如下:

$$
\begin{matrix}
a & 2a & 4a & 8a & 16a & 32a & \color{red}64a\\
3a & 6a & 12a & 24a & \color{red}48a & \color{red}96a \\
9a & 18a & \color{red}36a & \color{red}72a \\
27a & \color{red}54a \\
\color{red}81a
\end{matrix}
\tag{1}
$$

显然,如果把红色的数依次连接,就能构造出所有黑色的数。

这样构造一共使用了 \(\lfloor \log_2 \frac{n}{a}\rfloor + 2\) 个点,总共会使用 \(\sum\limits_{\gcd(6,a)=1}\lfloor \log_2 \frac{n}{a}\rfloor + 2 = \sum\limits_{i=1}^n[3 \not\mid i] + [\gcd(6,i)=1]\)

经过讨论,发现当 \(n \equiv 0,2,3,4 \pmod 6\) 时只需 \(\leq n\) 个点,但当 \(n \equiv 1,5 \pmod 6\) 时需要 \(n + 1\) 个点。

因此最后考虑一个优化:对于 \(x,y > \frac{n}{2}\)\(\gcd(x,6) = \gcd(y,6) = 1\),如果 \(\text{lcm}(x,y) \leq Y\),可以把 \(x,\text{lcm}(x,y),y\) 连成一条链省掉一个点。这样可以通过所有 \(n > 5\) 的数据了。

最后,特判一下 \(n=5\) 就行了:

5
10 15 6 8 4
1 2
2 3
3 4
4 5

参考代码(C++14):

#include<bits/stdc++.h>
using namespace std;
int n, X, ans[100005], vis[100005], tot = 0;
const int Y = 11000;
signed main(){
	freopen("tree.in", "r", stdin);
	freopen("tree.out", "w", stdout);
	cin >> n >> X;
	if(n == 5){
		cout << "5" << '\n';
		cout << "10 15 6 8 4" << '\n';
		cout << "1 2" << '\n';
		cout << "2 3" << '\n';
		cout << "3 4" << '\n';
		cout << "4 5" << '\n';
		return 0;
	}
	bool flag = false;
	for(int i = n / 2 + 1; i <= n; i++){
		for(int j = i + 1; j <= n; j++){
			if(__gcd(i, 6) == 1 && __gcd(j, 6) == 1){
				if(i / __gcd(i, j) * j <= Y){
					ans[++tot] = i;
					ans[++tot] = i / __gcd(i, j) * j;
					ans[++tot] = j;
					vis[i] = vis[i / __gcd(i, j) * j] = vis[j] = flag = true;
				}
			}
			if(flag) break;
		}
		if(flag) break;
	}
	for(int i = 1; i <= n; i++) if(__gcd(6, i) == 1){
		if(vis[i]) continue;
		vis[i] = true;
		int two = i;
		while(two <= n){
			int three = two;
			while(three <= n){
				three *= 3;
			}
			ans[++tot] = three;
			two *= 2;
		}
		ans[++tot] = two;
	}
	cout << tot << '\n';
	for(int i = 1; i <= tot; i++) cout << ans[i] << " \n"[i == tot];
	for(int i = 1; i < tot; i++) cout << i << ' ' << i + 1 << '\n';
	return 0;
}
posted @ 2025-11-07 16:00  zhang_kevin  阅读(27)  评论(0)    收藏  举报