暑假集训D12 2023.8.5 补题
NCPC 2022
A. Ace Arbiter 乒乓球
乒乓球的赛制是某方先赢 \(11\) 回合的获胜.首先 \(A\) 先发 \(1\) 发球,然后 \(B\) 连续 \(2\) 回合发球,然后 \(A\) 连续 \(2\) 回合发球,以此规则直至某一方先获得 \(11\) 分的,则获胜,并且比赛立即结束.
记分牌给出的是 \(X-Y\) ,其中 \(X\) 是当前回合发球的人的得到的分数 ,\(Y\)是当前回合的另一个人的得到的分数
现在给出按时间顺序排列的 \(n\) 个记分牌.问是否合法,如果不合法,输出第几个记分牌有误.
\(\operatorname{Solution}\)
以下是列举的所有不合法的情况
- 如果有人到达了 \(11\) 分,不管是谁,那么此时比赛已经停止,后面给出的比分不可以再变化.
- 如果有人在当前回合的分数比之前回合的分数更低,不合法.两个人分数一定是单调不减的
- 如果两个人都到 \(11\) 分,也是不合法的.
由于我们只知道某回合发球的人与接球的人的分数, 并不知道具体是 \(A\) 还是 \(B\) 的分数, \(X\) 和 \(Y\) 既可能是 \(A\) 也可能是\(B\) 的分数.下面问题就到了如何获取到两个人的分数.
注意到当前计分板上的总和,代表了已经完成的局数.
首先,第一回合是 \(Alice\) 发球,那么此时 \(X-Y\) 的分数就是 \(A\) 比 \(B\) 的分数.此时比分一定是 \(0-0\) ,已经完成的回合数 为 \(0\)
然后,第二三回合是 \(Bob\) 发球 ,已经完成的回合数为 \(1,2\) ,此时计分板上为 \(B-A\)
接着,第四五回合是 \(Alice\) 发球,已经完成的回合数为 \(3,4\) ,此时计分板上为 \(A-B\)
第 \(i\) 回合完成后的发球的规律为 \(ABBAABBAABB \cdots .i\in [0\sim 21]\)
那么已经完成的回合数 \(\% 4\) 后,结果为 \(1\) 或 \(2\) ,说明当前正在进行的是 \(Bob\) 发球,为 \(0\) 或 \(3\) 表示 \(Alice\) 发球.
那么给出一个比分后,我们根据比分的和立马就能知道 \(Alice\) 和 \(Bob\) 的比分了.
还有一个易错点是,判断有人达到 \(11\) 分后,要求后续比分不能再变化,判断时应该先判断这个比分是否合法,再判断后面的比分是否跟当前的比分相同.比赛时因为这个点导致一直 \(WA\) ,最后代码重构了一遍才过.
时间复杂度 \(\operatorname{O}(n)\)
反思
刚开始拿到这道题的时候没有读清楚题意,以为 \(X-Y\) 就是 \(A\) 比 \(B\) 的分数.并且同时样例还没有看仔细,有一个不能满足该条件的样例但是没有及时发现,导致出现非常低级的错误.到比赛开始后敲代码时才发现问题.由于前面思路出现问题,导致后面修修补补改了很多发也没有过.
#include<bits/stdc++.h>
using namespace std;
int x[110], y[110];
signed main()
{
int n;
cin >> n;
for (int i = 1; i <= n; i++)
{
scanf("%d-%d", &x[i], &y[i]);
}
int error = 0;
int preA = 0, preB = 0;
for (int i = 1; i <= n; i++)
{
if (x[i] == 11 && y[i] == 11)
{
error = i;
break;
}
int sum = x[i] + y[i];
int r = sum % 4;
int nB, nA;
if (r >= 1 && r <= 2)
{
nB = x[i];
nA = y[i];
}
else
{
nA = x[i];
nB = y[i];
}
if (nA < preA || nB < preB)
{
error = i;
break;
}
else
{
preA = nA;
preB = nB;
}
if (x[i] == 11 || y[i] == 11)
{
int t = i;
int j = i + 1;
while (j <= n)
{
if (x[j] != x[t] || y[j] != y[t])
{
error = j;
break;
}
j++;
}
if (error) break;
}
}
if (!error) cout << "ok";
else cout << "error " << error;
}
之后必须加强读题方面的训练,读题认真再认真,样例必须严谨检验,不能轻易下结论
B.Berry Battle 浆果和蚂蚁
现有一棵树,有 \(n-1\) 个结点,每个结点上都有一个浆果和一个蚂蚁,你需要拿走这棵树上的所有浆果,但是没有那么容易,每只蚂蚁在你拿走任意结点上的浆果后,它们都会向该结点的方向前进一步,如果他们在任意时刻的任意一个结点上汇聚(有一个结点上汇聚了所有蚂蚁),你就会被蚂蚁吃掉.
请你判断是否能给出一种取浆果的方案,使得你可以安全地取出所有浆果而不受攻击.若能,输出 YES
并给出取浆果方案,若不能,输出 NO
\(\operatorname{Solution 1}\)
两坨蚂蚁汇聚的情况有两种:
-
两坨蚂蚁在某结点的分支的最近结点上,然后取了通过分叉点路径的任意一点
-
两坨蚂蚁紧挨着,取了这两坨蚂蚁中的其中一坨.
首先证明:如果这颗树只有一个或没有度大于 \(1\) 的结点,则无解.
证:
\(\qquad\)当没有度大于 \(1\) 的结点,说明这棵树只有 \(1\) 或 \(2\) 个结点,显然无解.
\(\qquad\)当仅有一个度大于 \(1\) 的结点,类似于星型结构.由于只有一个度大于 \(1\) 的结点(即该中心结点),那么该结点的子树一定深度均为 \(1\) ,此时若取中心结点,那么所有结点便汇聚于中心结点,显然不合法.若取叶子结点,取完后,该叶子结点和中心结点都有蚂蚁,显然这时不能取中心结点,若取其他叶子结点,蚂蚁将分别移动到新取的这个叶子结点和中心结点,与前面情况类似.最终取完所有叶子结点的浆果后,该叶子结点和中心结点都各有一坨中心结点,显然是没法取出中心结点的浆果,即无解.证毕.
如果这棵树有两个或以上度大于 \(1\) 的结点,那么一定存在两个度大于 \(2\) 的结点满足这两个结点是相连的.记这两个结点为 \(u,v\).
取出 \(v\) 的浆果,此时 \(u\) 和 \(v\) 上一定都还有蚂蚁 ,然后再挑一个 \(v\) 的邻居 \(w (w\neq u)\) ,拿走浆果,此时 \(v,w\) 上一定有蚂蚁,但是这时已经没有蚂蚁了,那么我们只需要对剩下的结点按照 \(DFS\) 或 \(BFS\) 序遍历一遍即可.
时间复杂度 \(\operatorname{O}(n)\)
\(\operatorname{Solution 2}\)
树的重心:树上每个点到该点的距离最大值最小的点.这样的点到树的其他结点的距离是最均衡的
先找出这棵树的重心,取出重心处的浆果,然后以重心为中心开始向两边扩散地取点,类似于层次遍历,这样蚂蚁就只能左右横跳.
前提是重心的子树中一定要有一颗深度大于 \(1\) 的子树,否则在第一步取重心处的浆果时,所有的蚂蚁便汇聚了.
时间复杂度 \(\operatorname{O}(n)\)