『模拟赛题解』9.30 NOIP 模拟赛
9.30 模拟赛
T1. 光
Description
在一个 \(2 \times 2\) 的网格上有四盏灯,每个网格一盏。这四盏灯的位置分别是左上角,右上角,左下角,右下角。
每盏灯有一个可供调节的耗电量,耗电量越高,则灯对周围提供的亮度越多。具体来说,若某一盏灯的耗电量为 \(x\),那么它将会为自己的格子提供 \(x\) 的亮度, 为相邻的两个格子提供 \(\lfloor \! \frac{x}{2} \! \rfloor\) 的亮度,为对角的格子提供 \(\lfloor \! \frac{x}{4} \! \rfloor\)。其中 \(\lfloor x \rfloor\)表示对 \(x\) 向下取整。
某一个格子的亮度是四盏灯对它提供的亮度之和。例如左上角的灯耗电量为 \(4\), 右上角的灯耗电量为 \(7\),右下角的灯耗电量为 \(8\),左下角的灯耗电量为 \(0\),那么 左上角这个格子的亮度就是 \(4 + \lfloor \! \frac{7}{2} \! \rfloor + \lfloor \! \frac{8}{4} \! \rfloor + 0 = 9\) 。
现在我们对四个格子的最低亮度提出了要求,我们想要让四个格子的亮度都达到标准。你可以将每一盏灯的耗电量调节为任何一个大于等于零的整数,为了省电,你希望四盏灯的耗电量之和尽可能的小,请问四盏灯的最小耗电量之和是多小?
Solution
本题显然具有二分性,考虑二分答案。
假设总共有 \(mid\) 的耗电量可以分配给四盏灯,我们考虑如何分配:
- \(O(n^2)\) 枚举对角线的两盏灯的耗电量,假设枚举的是左上角 \(a\) 和右下角 \(b\)。那么我们可以发现,右上角 这个格子当前亮度为 \(\lfloor \! \frac{a}{2} \! \rfloor + \lfloor \! \frac{b}{2} \! \rfloor\),左下角的格子的亮度也相同。我们可以算出这两个格子的亮度与需求的差值,再将 \(mid - a - b\) 的耗电量分配给这两盏灯。
一种减少写代码细节的小技巧是:算出左下角格子的大致耗电量,然后用 for
循环在估计值的附近枚举 即可,这样就不需要判断边界条件了。
Code
#include <bits/stdc++.h>
using namespace std;
int a, b, c, d, ans;
bool check(int mid)
{
for (int i = 0; i <= b; i ++)
{
for (int j = 0; j <= c; j ++)
{
int x = max(b - i - j / 4, c - j - i / 4); // b, c 光量差
int f = mid - i - j; // 还能给的电
if (f / 2 < x)
continue;
int ac = max(0, a - i / 2 - j / 2), dc = max(0, d - j / 2 - i / 2); // a, d 光量差
for (int k = 0; k <= f; k ++) // 计算 a, d 的电
{
if (k + (f - k) / 4 >= ac && k / 4 + f - k >= dc)
return true;
}
}
}
return false;
}
int main()
{
freopen("light.in", "r", stdin);
freopen("light.out", "w", stdout);
cin >> a >> b >> c >> d;
int l = 0, r = a + b + c + d;
while (l <= r)
{
int mid = (l + r) >> 1;
if (check(mid))
ans = mid, r = mid - 1;
else
l = mid + 1;
}
cout << (ans ? ans : (a + b + c + d));
return 0;
}
T2. 爬
Description
鸡尾酒很喜欢让别人爬,于是他种了一棵树。然后在树上放了 \(n\) 只蚂蚁,看它们爬。
树上有 \(n\) 个点,用 \(n - 1\) 条边相连,除了根结点外,每个结点都有父亲,根结点记为 \(1\) 号结点,其它结点分别编号为 \(2 \sim n\)。
\(n\) 个点上分别放有一只蚂蚁, 并且每只蚂蚁有一个属性 \(a_i\),除了根节点的蚂蚁之外,每只蚂蚁都可以选择向上爬到父亲结点或者不爬(只能爬一次)。在所有蚂蚁都选择完毕之后,我们统计这些蚂蚁产生的快乐值:如果多只蚂蚁在同一点,则产生这些蚂蚁属性的异或和的快乐值。如果某个点上只有一只蚂蚁或者没有蚂蚁,则不产生快乐值。
问所有方案的所有快乐值之和。
Solution
考虑每个位置每个二进制位对答案的贡献。
例如考虑某一点 \(A\) 上的第 \(m\) 位二进制对答案的贡献。(第 \(m\) 位的权值是 \(2^m\))
统计 \(A\) 点以及 \(A\) 点的直接孩子(只有直接孩子才可以爬到 \(A\) 点)中,有多少只蚂蚁的第 \(m\) 位二进制是 \(1\),我们把这种蚂蚁称为好蚂蚁,不是 \(1\) 的则称为坏蚂蚁。
如果有奇数只好蚂蚁爬到 \(A\) 点,那么他们就可以异或出这个数字。假设好蚂蚁的数量为 \(n\),那么这些好蚂蚁爬到 \(A\) 点的方案数就是 \(C_n^1 + C_n^3 + C_n^5 + \dots = 2^{n - 1}\)。
然后我们还要统计其他对答案没有影响的蚂蚁的方案数,例如坏蚂蚁。或者是压根与 \(A\) 点无关的蚂蚁,要记得我们刚刚只是统计所有和 \(A\) 点直接相关的蚂蚁是好的还是坏的,但还有很多蚂蚁我们根本没有统计,那些蚂蚁也会形成一些方案。
每一只坏蚂蚁都可以向上爬或者不爬两种都行,那么我们这个方案数就是一个 \(2\) 的幂次;还有其他的蚂蚁,也是可以向上爬或者不爬,他的方案数也是 \(2\) 的幂次。但是我们要特殊判断根结点不能向上爬的情况,以及 \(A\) 点只有一只好蚂蚁的情况(如果某一点只有一只蚂蚁,那么它是不能异或对答案产生贡献的)
T3. 字符串
Description
你有 \(n\) 个字母 A
,\(m\) 个字母 B
,你可以将这些字母组成成为一个字符串,你需 要使得这个字符串的权值尽量大。现在我们以如下规则计算这个字符串的权值。
- 每有连续的 \(a\) 个
A
,且下一个字母依旧是A
,则权值 \(+1\)。假设 \(a = 3\),且连续有 \(7\) 个A
,那么根据此规则,权值 \(+2\)。你可以理解一段长度为 \(cnt_a\) 的A
所获得的权值为 \(\lfloor \! \frac{cnt_a - 1}{a} \! \rfloor\)。 - 每有连续的 \(b\) 个
B
,且下一个字母依旧是B
,则权值 \(+1\)。 - 上一个字母和当前字母不一样时,权值 \(+1\)。(第一个字母前面没有字母,也会使得权值 \(+1\),详见样例 1) 假设当前字母是
B
,则至少需要有连续 \(c\) 个字母B
,下一个字母才可以切换成A
。字母A
切换到字母B
没有任何限制。
请问你能构造的字符串权值最大可能是多少?
Solution
首先对三条规则进行简单分析:
- 对于规则 3 来说,假设 \(c =3\),那么如果想要通过
AB
切换来获得价值,那字符串就应该形如BBBABBBABBBA
这样,即每 \(3\) 个B
就切换成A
。 - 对于规则 1, 2 来说,把字母放在连续的一段地方,第一次产生价值需要 \(a+1\) 个
A
或者 \(b + 1\) 个B
,第二次及以后产生价值只需要 \(a\) 个A
或者 \(b\) 个B
。我们可以枚举A
和B
切换了多少次,假定我们切换的方式就是BBBABBBABBBA
,即每一段A
的长度都 为 \(1\),我们又知道AB
的切换次数,就可以知道现在已经用掉了几个A
。
然后我们先考虑把 A
全部用完。
- 在
BBBABBBA
的形式前面只需要花费一个A
就可以拿到一个价值。 - 然后将多余的
A
填充进字符串中,因为每一段A
的长度都是 \(1\),所以此时本质上是每多 \(a\) 个A
,答案 \(+1\)。
然后再考虑将剩余的 B
用完。
- 如果倒数第二个位置是
A
的话,那么最后一个只需要花费一个B
就能拿到一个价值。 - 例如 ,本来我们填充的是
BBBABBBABBBA
,根据规则 2,当一段B
的长度达到 \(5\) 的时候又可以使得价值 \(+1\),所以我们尽量将每一段B
都填充到长度为 \(5\)。如果全部填充好了且还有多余的B
,那么每多 \(b\) 个B
,答案 \(+1\)。
Code
#include <bits/stdc++.h>
using namespace std;
int n, m, a, b, c;
int solvea(int x)
{
return (x - 1) / a;
}
int solveb(int x)
{
return (x - 1) / b;
}
int main()
{
freopen("string.in", "r", stdin);
freopen("string.out", "w", stdout);
int tt;
cin >> tt;
while (tt --)
{
cin >> n >> m >> a >> b >> c;
int ans = 0;
for (int i = 0; i <= min(n, m / c); i ++)
{
int tans = 1;
if (i == 0) // n * A + m * B -> AAAAABBBB
{
tans += (n - 1) / a;
tans += (m - 1) / b;
}
else
{
tans += (i - 1) * 2; // ABB(+1)A(+1)BB...
if (n - i >= 1) // 还有剩余的 A
{
tans ++;
int x = n - i;
tans += solvea(x);
}
if (c > b) // ABB(+1)ABB(+1)... (c = 1)
tans += solveb(c) * i;
if (m - c * i >= 1) // 还有剩余的 B
{
tans ++;
int x = m - c * i - 1;
int k = (((c - 1) / b + 1) * b) - (c - 1);
if (x / k >= i)
{
tans += i;
x -= i * k;
}
else
{
tans += x / k;
x -= x / k * k;
}
tans += x / b;
}
}
ans = max(ans, tans);
}
ans ++;
cout << ans << endl;
}
return 0;
}
T4. 奇怪的函数
略。