『模拟赛题解』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,你可以将这些字母组成成为一个字符串,你需 要使得这个字符串的权值尽量大。现在我们以如下规则计算这个字符串的权值。

  1. 每有连续的 \(a\)A,且下一个字母依旧是 A,则权值 \(+1\)。假设 \(a = 3\),且连续有 \(7\)A,那么根据此规则,权值 \(+2\)。你可以理解一段长度为 \(cnt_a\)A 所获得的权值为 \(\lfloor \! \frac{cnt_a - 1}{a} \! \rfloor\)
  2. 每有连续的 \(b\)B ,且下一个字母依旧是 B,则权值 \(+1\)
  3. 上一个字母和当前字母不一样时,权值 \(+1\)。(第一个字母前面没有字母,也会使得权值 \(+1\),详见样例 1) 假设当前字母是 B,则至少需要有连续 \(c\) 个字母 B,下一个字母才可以切换成 A。字母 A 切换到字母 B 没有任何限制。

请问你能构造的字符串权值最大可能是多少?

Solution

首先对三条规则进行简单分析:

  1. 对于规则 3 来说,假设 \(c =3\),那么如果想要通过 AB 切换来获得价值,那字符串就应该形如 BBBABBBABBBA 这样,即每 \(3\)B 就切换成 A
  2. 对于规则 1, 2 来说,把字母放在连续的一段地方,第一次产生价值需要 \(a+1\)A 或者 \(b + 1\)B,第二次及以后产生价值只需要 \(a\)A 或者 \(b\)B。我们可以枚举 AB 切换了多少次,假定我们切换的方式就是 BBBABBBABBBA,即每一段 A 的长度都 为 \(1\),我们又知道 AB 的切换次数,就可以知道现在已经用掉了几个 A

然后我们先考虑把 A 全部用完。

  1. BBBABBBA 的形式前面只需要花费一个 A 就可以拿到一个价值。
  2. 然后将多余的 A 填充进字符串中,因为每一段 A 的长度都是 \(1\),所以此时本质上是每多 \(a\)A,答案 \(+1\)

然后再考虑将剩余的 B 用完。

  1. 如果倒数第二个位置是 A 的话,那么最后一个只需要花费一个 B 就能拿到一个价值。
  2. 例如 ,本来我们填充的是 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. 奇怪的函数

略。

posted @ 2023-10-02 19:46  Clyfort  阅读(35)  评论(0编辑  收藏  举报