暑假集训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}\)

两坨蚂蚁汇聚的情况有两种:

  • 两坨蚂蚁在某结点的分支的最近结点上,然后取了通过分叉点路径的任意一点
    image

  • 两坨蚂蚁紧挨着,取了这两坨蚂蚁中的其中一坨.

首先证明:如果这颗树只有一个或没有度大于 \(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)\)

C.Coffee Cup Combo 咖啡

D.Disc District 圆的直径

E.Enigmatic Enumeration 找最小环的个数

F.Foreign Football 名称矩阵

G.Graduation Guarantee 考试及格概率

H.Highest Hill 最高的山丘高度

posted @ 2023-08-05 20:50  LZH_03  阅读(46)  评论(0编辑  收藏  举报