2020-3-13 蓝桥杯校内模拟赛

        随便写点, 就当今天没有白过~。

A题

  • 题目:
            在计算机存储当中, 15.125GB是多少MB?

  • 分析:

        说的是在计算机存储当中, 细品一下, 第一题应该不会诈我, 嗯, 拿起计算器敲一遍: 15.125 X 1024 = 15488。不过既然题目强调在计算机存储当中来考虑这个数值, 会不会有其他情况下进制不同? 于是去的查了查, 有人说在硬盘存储 和 通信中是1000 进制, 卧槽? 第一题我感觉可能就被坑了。。。。。


B题

  • 题目:

        1200000 有多少个约数, 限制正约数?

  • 分析:

        刚看到约数还不知道什么鬼东西, 原来就是因数…, 要是题目不说限制正约数, 估计又要炸一题…, 算的应该是96(总感觉算错了…)


C题:

  • 题目:

        一颗包含2019个结点的树, 最多包含多少个叶结点?

  • 分析:

        再细品一下, 没说二叉树, 那就一个根节点, 2018 个叶子结点吧。


D题:

  • 题目:

        1~2019有多少个数包含9?

  • 分析:

        要是想口算绝对是憨憨…, 老老实实写个程序吧, 应该是 544


E题:

  • 题目:

问题描述
  小明对类似于 hello 这种单词非常感兴趣,这种单词可以正好分为四段,第一段由一个或多个辅音字母组成,第二段由一个或多个元音字母组成,第三段由一个或多个辅音字母组成,第四段由一个或多个元音字母组成。
  给定一个单词,请判断这个单词是否也是这种单词,如果是请输出yes,否则请输出no。
  元音字母包括 a, e, i, o, u,共五个,其他均为辅音字母。
输入格式
  输入一行,包含一个单词,单词中只包含小写英文字母。
输出格式
  输出答案,或者为yes,或者为no。
样例输入
lanqiao
样例输出
yes
样例输入
world
样例输出
no
评测用例规模与约定
  对于所有评测用例,单词中的字母个数不超过100。
C++

  • 分析:

        嗯, 上过编译原理的课, 自动机还是知道的, 老老实实写一个自动机。

  • 代码:
/**
 * 典型的 自动机 题目
 **/
#include 
using namespace std;
bool flag = false;          // 标记找辅音还是找元音
bool isyuan(char c)
{
    if(c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') return true;
    else return false;
}

bool is(string s)
{
    int i = 0, pre_i = 0;
    while(i < s.size() && !isyuan(s[i])) i++;           // 识别第一个辅音
    if(i == pre_i) return false;

    pre_i = i;
    while(i < s.size() && isyuan(s[i])) i++;            // 识别第二个元音
    if(i == pre_i) return false;

    pre_i = i;
    while(i < s.size() && !isyuan(s[i])) i++;            // 识别第三个辅音
    if(i == pre_i) return false;

    pre_i = i;
    while(i < s.size() && isyuan(s[i])) i++;            // 识别第四个元音
    if(i == pre_i) return false;

    return i == s.size();
}
int main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    string s;    
    cin >> s;
    if(is(s)) cout << "yes" << endl;
    else cout << "no" << endl;    
    return 0;
}

F题:

  • 题目:

问题描述
  在数列 a[1], a[2], …, a[n] 中,如果对于下标 i, j, k 满足 0   给定一个数列,请问数列中有多少个元素可能是递增三元组的中心。
输入格式
  输入的第一行包含一个整数 n。
  第二行包含 n 个整数 a[1], a[2], …, a[n],相邻的整数间用空格分隔,表示给定的数列。
输出格式
  输出一行包含一个整数,表示答案。
样例输入
5
1 2 5 3 5
样例输出
2
样例说明
  a[2] 和 a[4] 可能是三元组的中心。
评测用例规模与约定
  对于 50% 的评测用例,2 <= n <= 100,0 <= 数列中的数 <= 1000。
  对于所有评测用例,2 <= n <= 1000,0 <= 数列中的数 <= 10000。
C++

  • 分析:

        要是想套三个循环头给你打爆…, 维护两个数组smallbig, small[i] 表示 0~i 下标的当中的最小数, big[i] 表示i~n当中的最大数, 则O(n) 扫一遍, 对于某个数a[i], 如果 small[i-1] < a[i] < big[i + 1], 这就是一个中心。

  • 代码:
#include 
using namespace std;
int a[1000 + 5];
int big[1000 + 5];          // i ~ n 范围内的最大数
int small[1000 + 5];        // 0~i 范围内的最小数
int main()
{
#ifdef LOCAL
    freopen("F.in", "r", stdin);
#endif
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int n; cin >> n;
    for(int i = 0; i < n; ++i) cin >> a[i];
    
    small[0] = a[0], big[n - 1] = a[n - 1];
    for(int i = 1; i < n; ++i) small[i] = min(small[i - 1], a[i]);
    for(int i = n - 2; i >= 0; --i) big[i] = max(big[i + 1], a[i]);
    
    int ans = 0;
    for(int i = 1; i < n - 1; ++i)
    {
        if(a[i] > small[i - 1] && a[i] < big[i + 1]) ans += 1;
    }
    cout << ans << endl;
    return 0;
}

G题:

  • 题目:

问题描述
  一个正整数如果任何一个数位不大于右边相邻的数位,则称为一个数位递增的数,例如1135是一个数位递增的数,而1024不是一个数位递增的数。
  给定正整数 n,请问在整数 1 至 n 中有多少个数位递增的数?
输入格式
  输入的第一行包含一个整数 n。
输出格式
  输出一行包含一个整数,表示答案。
样例输入
30
样例输出
26
评测用例规模与约定
  对于 40% 的评测用例,1 <= n <= 1000。
  对于 80% 的评测用例,1 <= n <= 100000。
  对于所有评测用例,1 <= n <= 1000000。

  • 分析:

        没啥好说, O(n) 扫一遍判断就行。

#include 
using namespace std;
bool is(int n)
{
    int pre = -1;
    while(n)
    {
        if(pre == -1) pre = n % 10;
        else
        {
            int t = n % 10;
            if(pre < t) return false;
            pre = t;
        }
        n /= 10;
    }
    return true;
}
int main()
{
#ifdef LOCAL
    freopen("G.in", "r", stdin);
#endif
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int n, ans = 0; cin >> n;
    for(int i = 1; i <= n; ++i)
    {
        if(is(i)) ans += 1;
    }
    cout << ans << endl;
    return 0;
}

H题

  • 题目:

问题描述
  小明有一块空地,他将这块空地划分为 n 行 m 列的小块,每行和每列的长度都为 1。
  小明选了其中的一些小块空地,种上了草,其他小块仍然保持是空地。
  这些草长得很快,每个月,草都会向外长出一些,如果一个小块种了草,则它将向自己的上、下、左、右四小块空地扩展,这四小块空地都将变为有草的小块。
  请告诉小明,k 个月后空地上哪些地方有草。
输入格式
  输入的第一行包含两个整数 n, m。
  接下来 n 行,每行包含 m 个字母,表示初始的空地状态,字母之间没有空格。如果为小数点,表示为空地,如果字母为 g,表示种了草。
  接下来包含一个整数 k。
输出格式
  输出 n 行,每行包含 m 个字母,表示 k 个月后空地的状态。如果为小数点,表示为空地,如果字母为 g,表示长了草。
样例输入
4 5
.g…

…g…

2
样例输出
gggg.
gggg.
ggggg
.ggg.
评测用例规模与约定
  对于 30% 的评测用例,2 <= n, m <= 20。
  对于 70% 的评测用例,2 <= n, m <= 100。
  对于所有评测用例,2 <= n, m <= 1000,1 <= k <= 1000。

  • 分析:

        典型的BFS题目, 节点增广就完事了.

  • 代码:
/**
 * BFS 边界扩展
 */
#include 
using namespace std;
struct Node {
    int r, c;
    Node(int _r, int _c) : r(_r), c(_c){}
};        // BFS结点
int G[1000 + 1][1000 + 1];      // 草地
int dr[4] = {-1, 0, 1, 0};      // 上右下左
int dc[4] = {0, 1, 0, -1};     

int main()
{
#ifdef LOCAL
    freopen("H.in", "r", stdin);
#endif
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    queue Q;
    int n, m, k; cin >> n >> m;
    // 输入
    for(int i = 0; i < n; ++i)
    {
        for(int j = 0; j < m; ++j)
        {
            char c; cin >> c;
            if(c == 'g')
                G[i][j] = 1, Q.push(Node(i, j));
            else
                G[i][j] = 0;    
        }
    }    
    cin >> k;

    // BFS
    int month = 0;
    while(!Q.empty() && month < k)
    {
        int size  =Q.size();
        while(size--)
        {
            Node node = Q.front(); Q.pop();
            for(int i = 0; i < 4; ++i)
            {
                int nr = node.r + dr[i], nc = node.c + dc[i];
                
                // 没有越界, 且没有被访问过
                if(nr < 0 || nr >= n || nc < 0 || nc >= m || G[nr][nc] == 1) continue;
                
                // 此位置长草
                G[nr][nc] = 1;
                
                // 将此位置添加到队列等待进一步处理
                Q.push(Node(nr, nc));
            }
        }
        month += 1;
    }

    // 输出最终结果
    for(int i = 0; i < n; ++i)
    {
        for(int j = 0; j < m; ++j)
        {
            if(G[i][j] == 1) cout << 'g';
            else cout << ".";
        }
        cout << endl;
    }
    return 0;
}

I 题 :

  • 题目:

问题描述
  小明想知道,满足以下条件的正整数序列的数量:
  1. 第一项为 n;
  2. 第二项不超过 n;
  3. 从第三项开始,每一项小于前两项的差的绝对值。
  请计算,对于给定的 n,有多少种满足条件的序列。
输入格式
  输入一行包含一个整数 n。
输出格式
  输出一个整数,表示答案。答案可能很大,请输出答案除以10000的余数。
样例输入
4
样例输出
7
样例说明
  以下是满足条件的序列:
  4 1
  4 1 1
  4 1 2
  4 2
  4 2 1
  4 3
  4 4
评测用例规模与约定
  对于 20% 的评测用例,1 <= n <= 5;
  对于 50% 的评测用例,1 <= n <= 10;
  对于 80% 的评测用例,1 <= n <= 100;
  对于所有评测用例,1 <= n <= 1000。

  • 分析:

方案一:

        题目也太经典了, 完全可以映射出一大类题目: 画出解答树->寻找子问题->记忆化搜索->超时的话要么继续优化搜索, 要么转成动态规划的递推方式。我只想到对80%数据OK的 记忆化搜索, 还有20%超时, 转动态规划有大问题, 等等官网看看有没有题解出来瞅瞅…

方案二: 优化方案一

        原状态转移方程为:
f ( i , k ) = ∑ j = 1 k − 1 f ( j , ∣ i − j ∣ ) f(i, k) = \sum_{j = 1}^{k-1}f(j, |i-j|) f(i,k)=j=1k1f(j,ij)

        其中 i i i表示解答树当中某个结点的值, k k k 表示 i i i 与父亲结点的绝对值差。可见这个解答树非分支数量不确定, 导致在 D F S DFS DFS某个结点的时候会带一个循环, 这可能是导致时间效率低下的罪魁祸首, 方案二重新改写状态方程, 能将这个循环干掉, 通过将解答树的分支数量减少到少于 2 个 2个 2, 具体如下:

f ( i , j ) = f ( i , j − 1 ) + f ( j , ∣ i − j ∣ − 1 ) f(i, j) = f(i, j - 1) + f(j, |i - j| - 1) f(i,j)=f(i,j1)+f(j,ij1)

        其中 i i i表示某个解答树当中某个结点的值, 而 j j j表示其孩子的最大值是多少, 因此 f ( i , j ) f(i, j) f(i,j) 表示 结点 i i i 在其孩子的值 ≤ j \leq j j 时候的序列数, 以此就干掉了那个循环, 虽然导致了解答树深度变大, 但是经过测试, 效率确实显著提升。

  • 方案一代码:
/**
 * 统计解答树的除根节点以外的结点的数量
 */

#include 
using namespace std;
const int MODE = 10000;
int dp[1000 + 1][1000];             // 存放重复解

// 前一项和当前项
int dfs(int pre, int n)
{
    int _abs = abs(pre - n);
    
    // 重复问题
    if(dp[n][_abs]) 
    {
        return dp[n][_abs];
    }
    
    // dfs其子项, 后一项小于前两项的绝对值差
    int ans = 0;
    for(int i = 1; i < _abs; ++i)
    {
        ans = (ans + dfs(n, i)) % MODE;
    }
    
    // 小心不要忘记当前结点的数量;
    return dp[n][_abs] = (ans + 1) % MODE; 
}

int main()
{
#ifdef LOCAL
    freopen("", "r", stdin);
#endif
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int ans = 0, n; cin >> n;
    for(int i = 1; i <= n; ++i)
    {
        ans = (dfs(n, i) + ans) % MODE;
        //cout << ans << endl;
    }
    cout << ans << endl;
    return 0;
}
  • 方案二代码:
/**
	转变状态转移方程为 dp(i, j) 其表示为当前节点为i, 后继节点从 1 - j 的总的方案数。
	dp(i, j) = dp(i, j - 1) + dp(j, |i - j | - 1); 
**/
#include 
using namespace std;
const int MAXN = 1e3 + 1;
const int MODE = 1e4;

int dp[MAXN][MAXN];
int dfs(int i, int j)
{
    if(j <= 0) return 0;
    if(dp[i][j]) return dp[i][j]; 
    else return dp[i][j] = (dfs(i, j - 1) + dfs(j, abs(i - j) - 1) + 1) % MODE;
}
int main()
{	
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int n; cin >> n;
    cout << dfs(n, n) << endl;
    return 0;
}

J题

  • 题目:

问题描述
  小明要组织一台晚会,总共准备了 n 个节目。然后晚会的时间有限,他只能最终选择其中的 m 个节目。
  这 n 个节目是按照小明设想的顺序给定的,顺序不能改变。
  小明发现,观众对于晚上的喜欢程度与前几个节目的好看程度有非常大的关系,他希望选出的第一个节目尽可能好看,在此前提下希望第二个节目尽可能好看,依次类推。
  小明给每个节目定义了一个好看值,请你帮助小明选择出 m 个节目,满足他的要求。
输入格式
  输入的第一行包含两个整数 n, m ,表示节目的数量和要选择的数量。
  第二行包含 n 个整数,依次为每个节目的好看值。
输出格式
  输出一行包含 m 个整数,为选出的节目的好看值。
样例输入
5 3
3 1 2 5 4
样例输出
3 5 4
样例说明
  选择了第1, 4, 5个节目。
评测用例规模与约定
  对于 30% 的评测用例,1 <= n <= 20;
  对于 60% 的评测用例,1 <= n <= 100;
  对于所有评测用例,1 <= n <= 100000,0 <= 节目的好看值 <= 100000。

  • 分析:

        离散化的小技巧, 想想当年看俄罗斯方块儿还是没有白看 , 两次排序, 第一次按照喜欢度排序, 得到m个数, m个数在按照下标排序就OK

        这个第一个节目尽可能好看, 第二个节目在第一个节目的基础上尽可能好看, 说明第一个节目选择的范围为 a[0]~a[n -m], 第二个节目选择的范围在 a[last + 1] ~ a[n -m + 1], 依次类推下去, 直到选择出来所有的节目(这题意实在是太难理解了), 其中涉及到多次区间范围的内最值查询, 因此可以使用线段树来解决最值查询问题, 代码如下:

  • 代码:
#include 
#include 
#include 
#include 

using namespace std;
const int MAXN = 1e5;
struct Node
{
	int l;			/* 当前结点代表的区间的左边界*/
	int r;			/* 当前节点代表的区间的右边界*/
	int m_index;	/* 当前区间内的最大值的下标*/ 
}tree[4 * MAXN];	/* 线段树的节点*/ 

int a[MAXN + 1];

/**
	从根节点开始构建二叉树, 根节点从1开始计算。 
**/
void build(int root, int l, int r)
{
	tree[root].l = l;
	tree[root].r = r;
	// 构建叶子结点 
	if(l == r)
	{
		tree[root].m_index = l;
		// printf("%d   [%d, %d]  %d\n", root, l, r, tree[root].m_index);
		return;
	}
	
	// 构建其他结点
	int mid = (l + r) >> 1;
	int left = root << 1;
	int right = left + 1; 
	build(left, l, mid);
	build(right, mid + 1, r);
	tree[root].m_index = a[tree[left].m_index] > a[tree[right].m_index] ? tree[left].m_index : tree[right].m_index;
	// printf("%d   [%d, %d]  %d\n", root, l, r, tree[root].m_index);
}

/**
 * @param root : 当前节点 
 * @param ql   : 目标区间左边界 
 * @param qr   : 目标区间右边界 
 * @return     : [ql, qr] 区间内最大值 
 **/
int query(int root, int ql, int qr)
{
//	printf("[%d, %d] -- [%d, %d]\n", tree[root].l, tree[root].r, ql, qr);
//	Sleep(1000);
	// 如果节点区间在目标区间内, 则直接返回节点区间当中的最大值
	if(tree[root].l >= ql && tree[root].r <= qr) return tree[root].m_index;
	
	int mid = (tree[root].l + tree[root].r) >> 1;
	
	// 如果节点区间的左孩子和目标区间有交集
	int ans = -1;
	if(ql <= mid) ans = query(2 * root, ql, qr);
	
	
	// 如果节点区间的右孩子和目标区间有交集 
	if(qr > mid)
	{
		// 目标区间和节点区间的左孩子没有交集 
		if(ans == -1)
		{
			ans = query(2 * root + 1, ql, qr);
		}
		else
		{
			int _index = query(2 * root + 1, ql, qr);
			if(a[_index] > a[ans]) 
				ans = _index;
		}
	}
	return ans;
}
int main()
{
#ifdef LOCAL
	freopen("J.in", "r", stdin);
#endif
	int n, m; cin >> n >> m;
	for(int i = 1; i <= n; ++i)	cin >> a[i];
	build(1, 1, n);
	
	int _index = query(1, 1, 3);
	cout << a[_index] << endl;
	int ql = 1, qr = n - m + 1;
	bool first = true;
	while(m--)
	{
		int _index = query(1, ql, qr);
		cout << (first ? "" : " ") <<  a[_index], first = false;
		ql = _index + 1;
		qr++;
	}
	cout << endl;
}

你可能感兴趣的:(蓝桥)