Bootstrap

07/02/24 融合热身赛赛后总结&题解

一、总体情况

  • 考试一共有五道题。
  • 这次考试失误严重,C题非常水的一道题做了快两个小时,严重影响了心态和做其它题的时间。
  • 最终3个小时只做了 A , C A,C A,C 两道题。

二、题解

A题 二叉树深度

题目描述

有一个 n ( n ≤ 1 0 6 ) n(n \le 10^{6}) n(n106) 个结点的二叉树。给出每个结点的两个子结点编号(均不超过 n n n),建立一棵二叉树(根节点的编号为1),如果是叶子结点,则输入 0, 0。

建好这棵二叉树之后,请求出它的深度。二叉树的深度是指从根节点到叶子结点时,最多经过了几层。

思路分析

建好树之后跑一个dfs即可算出所有点的深度,取最大就是答案,每个点遍历一次,复杂度 O ( n ) O(n) O(n).

代码
#include <iostream>
#include <algorithm>

using namespace std;

struct Edge {
	int to;
	int next;
};

const int N = 1e6 + 6;

int n;
Edge edge[N];
int head[N];
int idx = 0;
int depth[N];
int ans = 0;

void add_edge(int from, int to)
{
	edge[idx].to = to;
	edge[idx].next = head[from];
	head[from] = idx++;
}

void dfs(int u, int father)
{
	depth[u] = depth[father] + 1;
	ans = max(ans, depth[u]);
	for (int i = head[u]; i != -1; i = edge[i].next)
		dfs(edge[i].to, u);
}

int main()
{
	ios::sync_with_stdio(false);
	fill(head, head + N, -1);
	cin >> n;
	int l, r;
	for (int i = 1; i <= n; i++) {
		cin >> l >> r;
		if (l != 0)
			add_edge(i, l);
		if (r != 0)
			add_edge(i, r);
	}
	dfs(1, 0);
	cout << ans << '\n';
	return 0;
}

B题 智商

题目描述

Doremy被要求测试 n n n个比赛。比赛 i i i只能在第 i i i天测试。比赛 i i i的难度为 a i a_{i} ai。一开始,Doremy的智商为 q q q。在第 i i i天,Doremy将决定是否测试比赛 i i i。只有当她的当前智商严格大于0时,她才能测试比赛。如果Doremy选择在第 i i i天测试比赛 i i i,将发生以下情:

  • 如果 a i > q a_i>q ai>q,Doremy会觉得自己不够聪明,所以 q q q 会减少 1 1 1
  • 否则,不会发生任何变化。

如果她选择不测试比赛,就不会发生任何变化。
Doremy希望尽可能测试更多的比赛。请给Doremy一个解决方案。

思路分析

这道题再选比赛的时候有两个显而易见的策略:

  • 智商尽量越在后面的时候降越好,这样才有机会体验更多前面的比赛.
  • 智商在最后一刻越接近于 0 0 0 越好

由此可得,这道题可以用贪心思想,策略就是刚才那两个。并且,由于确定的是最后的智商,所以可以倒过来枚举智商。

智商从 0 0 0 开始,倒着枚举,如果又一场比赛的值大于智商,则智商 + 1 +1 +1,一直枚举到智商 = q = q =q

代码
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1e5 + 5;

int t;
int n, q;
int a[N];
int vis[N];

void init()
{
	n = q = 0;
	fill(a, a + N, 0);
	fill(vis, vis + N, 0);
}

int main()
{
	ios::sync_with_stdio(false);
	cin >> t;
	while (t-- > 0) {
		init();
		cin >> n >> q;
		for (int i = 1; i <= n; i++)
			cin >> a[i];
		int tmp = 0;
		int i = n;
		for (i = n; i >= 1 && tmp < q; i--) {
			if (a[i] > tmp)
				tmp++;
			vis[i] = true;
		}
		for (; i >= 1; i--)
			if (a[i] <= q)
				vis[i] = true;
		for (int i = 1; i <= n; i++)
			if (vis[i])
				cout << 1;
			else
				cout << 0;
		cout << '\n';
	}
	return 0;
}

C题 科学家看电影

题目描述

莫斯科正在举办一次重要的国际会议,来自不同国家的科学家参加了会议。每个科学家都只知道一种语言。为方便起见,我们用 1 1 1 1 0 9 10^9 109 的整数枚举世界上所有语言。

会议结束后的晚上,所有科学家都决定去电影院。他们来到的电影院里有 m m m 部电影。每部电影都有两个不同的语言——音频语言和字幕语言。来到电影旁的科学家,如果听得懂电影的音频,他会非常高兴,如果他看得懂字幕,他会很满意,如果他既听不懂音频也看不懂字母,他会非常生气(注意,每部电影的音频语言和字幕语言总是不同的)。

科学家们决定一起去看同一部电影。你必须帮助他们选择电影,这样非常高兴的科学家的数量是尽可能多的。如果有几部这样的电影,请从中选择一部能够最大限度地增加满意的科学家的数量。

思路分析

这道题其实非常简单,可以用map或者离散化将每种语言会的人数统计出来,再在电影里面以电影语言对应的人数从大到小排序。并且,音频语言的人数如果相等的话,再以字幕语言的人数排序。这样,排完序后,第一个电影就是答案。

代码
#include <bits/stdc++.h>

using namespace std;

const int N = 2e5 + 5;

map<int, int> mp;	//每种语言出现的次数

int n, m;
int a[N];

struct Node {
	int a;			//音频语言
	int b;			//字幕语言
	int r_a;		//听得懂音频语言的人数
	int r_b;		//看得懂字幕语言的人数
	int idx;		//电影编号
};

bool cmp(Node a, Node b)
{
	//优先按音频排序
	if (a.r_a != b.r_a)
		return a.r_a > b.r_a;
	return a.r_b > b.r_b;
}

Node f[N];

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >> a[i];
		mp[a[i]]++;
	}
	cin >> m;
	for (int i = 1; i <= m; i++) {
		cin >> f[i].a;
		f[i].r_a = mp[f[i].a];
		f[i].idx = i;
	}
	for (int i = 1; i <= m; i++) {
		cin >> f[i].b;
		f[i].r_b = mp[f[i].b];
	}
	sort(f + 1, f + m + 1, cmp);
	cout << f[1].idx;
	return 0;
}

D题 寻找段落

题目描述

给定一个长度为 n n n 的序列 a a a,定义 a i a_i ai 为第 i i i 个元素的价值。现在需要找出序列中最有价值的“段落”。段落的定义是长度在 [ S , T ] [S, T] [S,T] 之间的连续序列。最有价值段落是指平均值最大的段落。

段落的平均值 等于 段落总价值 除以 段落长度

思路分析

这道题的答案是满足单调性的,若答案越大,则能产生这个答案的区间就越少;若答案越小,则区间就越多。所以,可以考虑二分答案。

二分答案的关键就在于check()函数,而这道题的check()显然就是要确认有没有区间的平均值 ≥ m i d d l e \ge middle middle,换句话说,可以把每个元素都减去一个 m i d d l e middle middle,再用前缀和来算区间和,如果区间和 ≥ 0 \ge 0 0,则返回true。而这个平均值可以用单调队列维护。

假设以一个下标 i i i 作为区间的右端点,则区间 [ i − s + 1 , i ] [i - s + 1, i] [is+1,i] 和区间 [ i − t + 1 , i ] [i - t + 1, i] [it+1,i] 是合法的。那么就可以用一个单调递增队列 q q q 来维护这些区间,单调队列中的队首是非常好弹出的,当 i − q i - q iq 的队首 > t > t >t 的时候,那 q q q 的队首就要出队。

但比较难维护的是队尾。我们知道,区间 [ i − s + 1 , i ] [i - s + 1, i] [is+1,i] 的区间和 = s u m [ i ] − s u m [ i − s ] = sum[i] - sum[i - s] =sum[i]sum[is],而我们就要尽量让这个值 ≥ 0 \ge 0 0。 队尾就要尽量小。当 q q q 不为空且 s u m [ q sum[q sum[q的队尾 ] > s u m [ i − s ] ]> sum[i - s] ]>sum[is],就弹出队尾。

代码
#include <iostream>
#include <iomanip>
#include <queue>

#define N 100005
#define DELTA 1e-6

using namespace std;

int n;
int s, t;
int nums[N];
double sum[N];

bool check(double middle)
{
	for (int i = 1; i <= n; i++)
		sum[i] = sum[i - 1] + nums[i] - middle;
	deque<int> q;
	for (int i = 1; i <= n; i++)
		if (i >= s) {
			while (!q.empty() &&  sum[q.back()] - sum[i - s]> DELTA)
				q.pop_back();
			q.push_back(i - s);
			while (i - q.front() > t)
				q.pop_front();
			if (sum[i] > sum[q.front()])
				return true;
		}
	return false;
}

int main()
{
	ios::sync_with_stdio(false);
	cin >> n >> s >> t;
	for (int i = 1; i <= n; i++)
		cin >> nums[i];
	double left = 0, right = 1e9;
	double middle = (left + right) / 2;
	while (right - left > DELTA) {
		middle = (left + right) / 2;
		if (check(middle))
			left = middle;
		else
			right = middle;
	} 
	cout << fixed << setprecision(3) << left << '\n';
	return 0;
}

E题 恢复序列

题目描述

Peter 在白板上写下一个严格递增的正整数序列 a 1 ,   a 2 ,   . . . ,   a n a_{1}, a_{2}, ..., a_{n} a1,a2, ...,an。然后,Vasil 将这个序列中的数的某些位替换成为问号。这样一来,每个问号恰好对应了一个丢失的位。
依据白板上剩余的已知位,恢复原始的序列。

思路分析

这道题其实就是一个超级烦的模拟题,带一点点的贪心。首先,输入时,要把每个数当作字符串输入。其次,我们知道,为了让下一个数有得填,那上一个数填的就要尽可能的少,这就是贪心策略

剩下的就是分类讨论了,假设当前的数为 n o w now now,上一个数为 l a s t last last,(如果第一个数有问号全部填 0 0 0,除非第一位有问号,那第一位就填 1 1 1,其它的问号填 0 0 0)

  • n o w now now 的位数 > > > l a s t last last 的位数,则 n o w now now 中的问号尽量都填 0 0 0 (除了第一位);
  • n o w now now 的位数 < < < l a s t last last 的位数,则直接输出NO,结束程序;
  • n o w now now 的位数 = = = l a s t last last 的位数,则又要分好几种情况:这时候先把问号忽略,再比较 n o w now now l a s t last last 的大小,再根据每种情况分类讨论即可。
代码
#include <iostream>
#include <string>
#include <vector>

#define now v[idx]
#define last v[idx - 1]

using namespace std;

const int N = 1e5 + 5;

string v[N];

bool solve(int idx)
{
    bool res = true;
    if (now.size() < last.size()) {
        res = false;
        return res;

    } else if (now.size() > last.size()) {
        if (now[0] == '?')
            now[0] = '1';
        for (int i = 1; i < now.size(); i++)
            if (now[i] == '?')
                now[i] = '0';

    } else {
        // now.size() == last.size()
        string op = "";
        int pos = 0;
        for (int i = 0; i < now.size(); i++) {
            if (now[i] == '?' || now[i] == last[i])
                continue;

            if (now[i] < last[i]) {
                op = "now < last", pos = i;
                break;

            } else if (now[i] > last[i]) {
                op = "now > last", pos = i;
                break;
            }
        }
        if (op == "")
            op = "now == last";

        if (op == "now < last") {
            bool flag = false;
            bool wrong = false;

            for (int i = pos - 1; i >= 0; i--)
                if (now[i] == '?') {
                    flag = true;
                    if (last[i] == '9') {
                        wrong = true;
                        continue;

                    } else {
                        now[i] = last[i] + 1;
                        wrong = false;
                        for (int j = i; j >= 0; j--)
                            if (now[j] == '?')
                                now[j] = last[j];
                        break;
                    }
                }

            if (!flag || wrong) {
                res = false;
                return res;
            }

            for (int i = now.size() - 1; i >= 0; i--)
                if (now[i] == '?')
                    now[i] = '0';

        } else if (op == "now > last") {
            for (int i = 0; i <= pos; i++)
                if (now[i] == '?')
                    now[i] = last[i];
            for (int i = pos + 1; i < now.size(); i++)
                if (now[i] == '?')
                    now[i] = '0';

        } else if (op == "now == last") {
            bool flag = false;
            bool wrong = false;

            for (int i = now.size() - 1; i >= 0; i--)
                if (now[i] == '?') {
                    flag = true;

                    if (last[i] == '9') {
                        now[i] = '0';
                        wrong = true;
                        continue;

                    } else {
                        now[i] = last[i] + 1;
                        wrong = false;
                        for (int j = i; j >= 0; j--)
                            if (now[j] == '?')
                                now[j] = last[j];
                        break;
                    }
                }

            if (!flag || wrong) {
                res = false;
                return res;
            }
        }
    }
    if (now.size() == last.size() && now <= last) {
        res = false;
    }
    return res;
}

int main()
{
    ios::sync_with_stdio(false);
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> v[i];
    solve(1);
    for (int i = 2; i <= n; i++)
        if (!solve(i)) {
            cout << "NO\n";
            return 0;
        }
    cout << "YES\n";
    for (int i = 1; i <= n; i++)
        cout << v[i] << '\n';
    return 0;
}

三、存在的不足

  • 这次考试在心态方面吃了很大的亏,在做玩 A A A 题之后,我一直再想 B B B 题,有点思路,但还在想,这时我看到有人已经把 C C C 题做出来了。此时,我已经开始慌了,我也去看向了 C C C 题。但是,当时我把 C C C 题想得太复杂了,原本统计完之后再排个序就能解决的问题,我在那里写了3个map。写了半天没写出来后,我发现大部分人都已经把 C C C 题做出来了,我这个时候已经慌到离谱,~~写了个set<int, int> st;都还没发现哪里写错了。~~后来,我慢了下来,在还有50分钟结束考试的时候,突然想到了这个做法,写完代码再交上去一共就用了10分钟不到……
  • 并且,这次考试中,我就是因为把问题想得太复杂了,导致丢失了大量时间,就和这回的 E E E 题一样, E E E 题我在考试时肯定没时间再做了,但当考完后我补题的时候,也是想复杂了,写了一大堆bool变量和一堆if…else…,也是想复杂了,还把自己绕晕了。后来,我又重新理了一下思路,重新写了一遍代码,很快就把这道题 A A A了。

四、下次考试时的改进措施

  • 考试时能不看排名就不看排名,这玩意是真的影响心态……
  • 做题前先把思路彻底理清楚再开写
  • 考试时如果有题实在不会做,但时间充裕,可以先打个暴力交上去,说不定就过了。(这次的 D D D 题暴力都能过);
  • 对于简单问题,多想想有什么算法和这个问题很像,不要盲目开写,更不要想复杂了。

谢谢大家阅读

;