『模拟赛题解』9.27 NOIP 模拟赛
9.27 NOIP 模拟赛
T1. 小 O 的珠子
Description
小 O 有一些很漂亮的珠子,根据小 O 对珠子的喜欢程度,编号为 a 到 z 。 珠子们之间用魔力相互吸引,排列成一条线。
有一天,小 Y 乱丢法术,一不小心把某些珠子之间的魔力消除了,珠子们断成了 \(n\) 条。
现在,小 O 想知道,将断开的 \(n\) 条珠子们重新排列,能得到的字典序最小的序列是什么。
Solution
赛时思路
直接将所有字符串 sort 一遍。
实际得分 \(0\) pts。
正解
那上述思路哪里错了呢 ?
错就错在排序的规则。
对于 \(s_1, s_2\),如果 \(s_1,s_2\) 字典序比 \(s_2,s_1\) 小,我们定义为 \(s_1 < s_2\)。显然,如果 \(s_1 < s_2\),则交换 \(s_1, s_2\) 更优。
所以排序时让 \(s_1 + s_2\) 和 \(s_2 + s_1\) 比较。
Code
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 5;
int n;
string s[maxn];
string ans;
bool cmp(string x, string y)
{
return x + y < y + x;
}
int main()
{
freopen("bead.in", "r", stdin);
freopen("bead.out", "w", stdout);
cin >> n;
for (int i = 1; i <= n; i ++)
cin >> s[i];
sort (s + 1, s + n + 1, cmp);
for (int i = 1; i <= n; i ++)
ans += s[i];
cout << ans;
return 0;
}
T2. 王国的传送门
Description
一个王国有 \(n\) 个城镇,编号为 \(1\) 到 \(n\)。城镇 \(1\) 是首都。
王国中的每个城镇都有一个单向传送门,可以将人从一个城镇快速传送到另一个城镇。
城镇 \(i\) 的传送目标是城镇 \(a_i(1 \le a_i \le n)\)。通过多次使用传送门,可以保证从任何城镇到达首都。
国王洛浔喜欢整数 \(K\) 。洛浔想要调整一些传送门的目的地(出发地不变),以便从任何城镇开始,使用传送门恰好 \(K\) 次后将可以到达首都。并且,洛浔允许这 \(K\) 次中重复使用任意传送门。
请你帮洛浔找到需要调整的传送门的最小数量(允许存在自环)。
Solution
赛时思路
同下。
正解
不难发现,城镇 \(1\) 的传送门只能传送到 \(1\),否则不可能保证 \(1 \to 1\) 使用传送门恰好 \(K\) 次。
证明如下:
所以只需要用 DFS
跑一边,判断每个点到 \(1\) 的距离,如果 \(\ge K\),就将这个点向上走 \(K - 1\) 步的祖先给连向 \(1\)。
Code
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 5;
int n, k, ans, cnt;
int head[maxn];
struct Edge
{
int nxt, to;
} edge[maxn];
void addedge(int x, int y)
{
edge[++ cnt].to = y;
edge[cnt].nxt = head[x];
head[x] = cnt;
}
int dfs(int x, int fa)
{
int ret = 0;
for (int i = head[x]; i; i = edge[i].nxt)
ret = max(ret, dfs(edge[i].to, x));
ret ++;
if (ret >= k && fa != 1)
{
ans ++;
ret = 0;
}
return ret;
}
int main()
{
freopen("gate.in", "r", stdin);
freopen("gate.out", "w", stdout);
cin >> n >> k;
for (int i = 1; i <= n; i ++)
{
int x;
cin >> x;
if (i == 1 && x != 1)
ans = 1;
else if (i != 1)
addedge(x, i);
}
dfs(1, 1);
cout << ans;
return 0;
}
T3. wwk与黑白树
Description
wowaka 给你一棵树,上面有 \(n\) 个节点。一开始所有边都是黑色的。
每次你可以选择树上一条所有边都 是黑色的路径,删掉其中一条边,然后在路径的两个端点之间连一条白色的边。求最后能否得到目标形态(都是白色的边)的树。
Solution
赛时思路
没写。
正解
考虑两棵树合在一起,那么如果两点之间存在两条边(称为 \((x,y)\) ),则可以将度数较小的 \(x\) 的连边全部转移到 \(y\) 上,类似于缩点。
Complexity
时间复杂度:\(O(n \log (n)^2)\)。
Code
#include <bits/stdc++.h>
using namespace std;
const int maxn = 5e4 + 5;
int n;
map < pair <int, int>, int> p;
queue < pair <int, int> > q;
multiset <int> v[maxn];
int main()
{
freopen("wowaka.in", "r", stdin);
freopen("wowaka.out", "w", stdout);
int tt;
cin >> tt;
while (tt --)
{
cin >> n;
p.clear();
for (int i = 1; i <= n; i ++)
v[i].clear();
while (!q.empty())
q.pop();
for (int i = 1; i < 2 * n - 1; i ++)
{
int x, y;
cin >> x >> y;
if (x < y)
swap(x, y);
v[x].insert(y);
v[y].insert(x);
p[{x, y}] ++;
if (p[{x, y}] == 2)
q.push({x, y});
}
int x = 0;
while (!q.empty())
{
if (x >= n - 1)
break;
pair <int, int> k;
k = q.front();
q.pop();
if (k.first < k.second)
swap(k.first, k.second);
if (!p[k])
continue;
if (v[k.first].size() > v[k.second].size())
swap(k.first, k.second);
for (auto it = v[k.first].begin(); it != v[k.first].end(); it ++)
{
int i = *it;
v[i].erase(v[i].find(k.first));
p[{max(i, k.first), min(i, k.first)}] --;
if (k.second != i)
{
v[k.second].insert(i);
v[i].insert(k.second);
p[{max(i, k.second), min(i, k.second)}] ++;
if (p[{max(i, k.second), min(i, k.second)}] == 2)
q.push({max(i, k.second), min(i, k.second)});
}
}
v[k.first].clear();
x ++;
}
cout << (x >= n - 1 ? "YES\n" : "NO\n");
}
return 0;
}
T4. 反复横跳
略。
Others
T2 证明过程:感谢 Pretharp 的贡献。