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

浙公网安备 33010602011771号