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\) 的数,写成一个杨表:
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;
}

浙公网安备 33010602011771号