AcWing - 蓝桥杯集训每日一题(DAY 6——DAY 10)

一、AcWing 1497. 树的遍历(简单)

题目描述

一个二叉树,树中每个节点的权值互不相同。
现在给出它的后序遍历和中序遍历,请你输出它的层序遍历。

输入格式

第一行包含整数 N N N,表示二叉树的节点数。
第二行包含 N N N 个整数,表示二叉树的后序遍历。
第三行包含 N N N 个整数,表示二叉树的中序遍历。

输出格式

输出一行 N N N 个整数,表示二叉树的层序遍历。

数据范围

1 ≤ N ≤ 30 1≤N≤30 1N30
官方并未给出各节点权值的取值范围,为方便起见,在本网站范围取为 1 ∼ N 1∼N 1N

输入样例

7
2 3 1 5 7 6 4
1 2 3 4 5 6 7

输出样例

4 1 6 3 5 7 2

具体实现

1. 实现思路

  • 在最开始,我们需要明白前序遍历、中序遍历、后序遍历和层序遍历分别都是什么。
  • 前序遍历就是根在前,从左往右,一棵树的根永远在左子树前面,左子树永远在右子树前面。
  • 中序遍历就是根在中,从左往右,一棵树的左子树永远在根前面,根永远在右子树前面。
  • 后序遍历就是根在后,从左往右,一棵树的左子树永远在右子树前面,右子树永远在根前面。
  • 层序遍历就是就是按层,从上到下,从左到右遍历。
  • 以下图为例进行四种遍历方式的结果输出。

在这里插入图片描述

  • 前序遍历就是 1-2-3-4-7-5-6。
  • 中序遍历就是 3-2-4-1-5-7-6。
  • 后序遍历就是 3-4-2-5-6-7-1。
  • 层序遍历就是 1-2-7-3-4-5-6。
  • 在明确上述定义之后,我们开始对样例进行模拟。
  • 由于在后序遍历中,根节点放在最后,因此,4 就是根节点。同时根据 4 在中序遍历中的位置信息,我们可以判断出 1-2-3 是中序遍历根节点 4 的左子树,5-6-7 是中序遍历根节点 4 的右子树。
  • 同样的道理,我们就可以根据递归的思想,判断出整个二叉树的结构了,如下图所示。

在这里插入图片描述

在这里插入图片描述

  • 我们使用 vector 容器对每一层的元素进行存储,然后输出。

2. 实现代码

#include <bits/stdc++.h>
using namespace std;
const int N=35;
int n;
//a[N]表示后序遍历
//b[N]表示中序遍历 
//p[N]存储在中序遍历中每一个数值的位置 
int a[N],b[N],p[N];
//每一层开一个vector用来记录每一层的层序遍历
vector<int>level[N]; 
//传入a数组的起点和终点,b数组的起点和终点,d表示层数 
void build(int al,int ar,int bl,int br,int d)
{
    
    
	if(al>ar)
	{
    
    
		return ;
    }
    int val=a[ar]; //val表示根节点 
    level[d].push_back(val); 
    int k=p[val]; //p[val]表示根节点在b数组中的位置 
    build(al,al+k-1-bl,bl,k-1,d+1); //左子树的中序遍历和后序遍历 
    build(al+k-bl,ar-1,k+1,br,d+1); //右子树的中序遍历和后序遍历 
}
int main()
{
    
    
	cin>>n;
	for(int i=0;i<n;i++)
	{
    
    
		cin>>a[i];
	} 
	for(int i=0;i<n;i++)
	{
    
    
		cin>>b[i];
	}
	for(int i=0;i<n;i++)
	{
    
    
		p[b[i]]=i;
	}
	build(0,n-1,0,n-1,0);
	for(int i=0;i<n;i++)
	{
    
    
		for(int x: level[i])
		{
    
    
			cout<<x<<" ";
		}
	}
	return 0;
}

二、AcWing 1249. 亲戚(简单)

题目描述

或许你并不知道,你的某个朋友是你的亲戚。
他可能是你的曾祖父的外公的女婿的外甥女的表姐的孙子。
如果能得到完整的家谱,判断两个人是否是亲戚应该是可行的,但如果两个人的最近公共祖先与他们相隔好几代,使得家谱十分庞大,那么检验亲戚关系实非人力所能及。
在这种情况下,最好的帮手就是计算机。
为了将问题简化,你将得到一些亲戚关系的信息,如 Marry 和 Tom 是亲戚,Tom 和 Ben 是亲戚,等等。
从这些信息中,你可以推出 Marry 和 Ben 是亲戚。
请写一个程序,对于我们的关于亲戚关系的提问,以最快的速度给出答案。

输入格式

输入由两部分组成。
第一部分以 N , M N,M N,M 开始。 N N N 为问题涉及的人的个数。这些人的编号为 1 , 2 , 3 , … , N 1,2,3,…,N 1,2,3,,N。下面有 M M M 行,每行有两个数 a i , b i a_{i},b_{i} ai,bi,表示已知 a i a_{i} ai b i b_{i} bi 是亲戚。
第二部分以 Q Q Q 开始。以下 Q Q Q 行有 Q Q Q 个询问,每行为 c i , d i c_{i},d_{i} ci,di,表示询问 c i c_{i} ci d i d_{i} di 是否为亲戚。

输出格式

对于每个询问 c i , d i c_{i},d_{i} ci,di,输出一行:若 c i c_{i} ci d i d_{i} di 为亲戚,则输出 Yes,否则输出 No

数据范围

1 ≤ N ≤ 20000 1≤N≤20000 1N20000
1 ≤ M ≤ 1 0 6 1≤M≤10^{6} 1M106
1 ≤ Q ≤ 1 0 6 1≤Q≤10^{6} 1Q106

输入样例

10 7
2 4
5 7
1 3
8 9
1 2
5 6
2 3
3
3 4
7 10
8 9

输出样例

Yes
No
Yes

具体实现

1. 实现思路

  • 第一个操作可以理解为合并两个集合,第二个操作可以理解为判断两个点是否在同一个集合当中。

2. 实现代码

#include <bits/stdc++.h>
using namespace std;
const int N = 20010;
int n, m;
int p[N];
int find(int x)
{
    
    
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}
int main()
{
    
    
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ ) p[i] = i;
    while (m -- )
    {
    
    
        int a, b;
        scanf("%d%d", &a, &b);
        p[find(a)] = find(b);
    }

    scanf("%d", &m);
    while (m -- )
    {
    
    
        int a, b;
        scanf("%d%d", &a, &b);
        if (find(a) == find(b)) puts("Yes");
        else puts("No");
    }
    return 0;
}

三、AcWing 2058. 笨拙的手指(简单)

题目描述

奶牛贝茜正在学习如何在不同进制之间转换数字。
但是她总是犯错误,因为她无法轻易的用两个前蹄握住笔。
每当贝茜将数字转换为一个新的进制并写下结果时,她总是将其中的某一位数字写错。
例如,如果她将数字 14 14 14 转换为二进制数,那么正确的结果应为 1110 1110 1110,但她可能会写下 0110 0110 0110 1111 1111 1111
贝茜不会额外添加或删除数字,但是可能会由于写错数字的原因,写下包含前导 0 0 0 的数字。
给定贝茜将数字 N N N 转换为二进制数字以及三进制数字的结果,请确定 N N N 的正确初始值(十进制表示)。

输入格式

第一行包含 N N N 的二进制表示,其中一位是错误的。
第二行包含 N N N 的三进制表示,其中一位是错误的。

输出格式

输出正确的 N N N 的值。

数据范围

0 ≤ N ≤ 1 0 9 0≤N≤10^{9} 0N109 ,且存在唯一解。

输入样例

1010
212

输出样例

14

样例解释

14 14 14 在二进制下的正确表示为 1110 1110 1110,在三进制下的正确表示为 112 112 112

具体实现

1. 实现思路

  • 这个题是给我们一个数在二级制下表示的结果和在三进制下表示的结果,但是这两个表示方法都只有一位是写错的。
  • 这道题对时间的要求比较低,可以直接使用暴力枚举进行求解,但是正向枚举到 1 0 9 10^{9} 109 容易出现超时问题,因此,我们需要反向枚举一下。
  • n 在二进制表示下最多有 30 位,并且恰好只有一位是错位的,因此,每位就只有一种情况,一共有 30 种情况。
  • 同理,三进制每一位写错也都枚举一遍,但三进制每一位有两种选择,因此,一共有 60 种情况。
  • 二进制集合和三进制集合的交集就是我们求解的答案。
  • 这里需要注意的是,写错的二进制数和三进制数是可能出现前导 0 的,但正确的二进制数和三进制数是绝对不会出现前导 0 的。
  • 小技巧:
  • 当奇数异或 1 时,就相当于这个奇数减去 1。
  • 当偶数异或 1 时,就相当于这个偶数加上 1。

2. 实现代码

#include <bits/stdc++.h>
using namespace std;
int base(string s,int b)  //将b进制的数转换成10进制 
{
    
    
	int res=0;
	for(int i=0;i<s.size();i++)
	{
    
    
		res=res*b+s[i]-'0';
    }
    return res;
}
int main()
{
    
    
	//x是二进制表示的结果 
	//y是三进制表示的结果 
	string x,y; 
	cin>>x>>y; 
	unordered_set<int>hash; 
	for(int i=0;i<x.size();i++)
	{
    
    
		string s=x;
		s[i]^=1;  //0,1互相转换 
		if(s.size()>1&&s[0]=='0')
		{
    
    
			continue;
		}
		hash.insert(base(s,2));
	}
	for(int i=0;i<y.size();i++)
	{
    
    
		for(int j=0;j<3;j++)
		{
    
    
			if(y[i]-'0'!=j)
			{
    
    
				string s=y;
				s[i]=j+'0';// 0,1,2相互转换 
				if(s.size()>1&&s[0]=='0')
				{
    
    
					continue;
				}
				int n=base(s,3);
				if(hash.count(n))
				{
    
    
					cout<<n<<endl;
				}
			}
		}
	}
	return 0;
}

四、AcWing 299. 裁剪序列(困难)

题目描述

给定一个长度为 N N N 的序列 A A A,要求把该序列分成若干段,在满足每段中所有数的和不超过 M M M 的前提下,让每段中所有数的最大值之和最小。
试计算这个最小值。

输入格式

第一行包含两个整数 N N N M M M
第二行包含 N N N 个整数,表示完整的序列 A A A

输出格式

输出一个整数,表示结果。
如果结果不存在,则输出 − 1 −1 1

数据范围

0 ≤ N ≤ 1 0 5 0≤N≤10^{5} 0N105
0 ≤ M ≤ 1 0 11 0≤M≤10^{11} 0M1011
序列 A A A 中的数非负,且不超过 1 0 6 10^{6} 106

输入样例

8 17
2 2 2 8 1 8 2 1

输出样例

12

具体实现

1. 实现思路

  • 这道题对本人而言过于困难,听 y 总讲解过后依旧难以实现,因此,我就直接把 y 总的讲解视频链接放到下方。
  • 讲解视频链接

2. 实现代码

#include <iostream>
#include <cstring>
#include <algorithm>
#include <set>

using namespace std;

typedef long long LL;

const int N = 100010;

int n;
LL m;
int w[N], q[N];
LL f[N];
multiset<LL> S;

void remove(LL x)
{
    
    
    auto it = S.find(x);
    S.erase(it);
}

int main()
{
    
    
    scanf("%d%lld", &n, &m);
    for (int i = 1; i <= n; i ++ )
    {
    
    
        scanf("%d", &w[i]);
        if (w[i] > m)
        {
    
    
            puts("-1");
            return 0;
        }
    }

    int hh = 0, tt = -1;
    LL sum = 0;
    for (int i = 1, j = 1; i <= n; i ++ )
    {
    
    
        sum += w[i];
        while (sum > m)
        {
    
    
            sum -= w[j ++ ];
            if (hh <= tt && q[hh] < j)
            {
    
    
                if (hh < tt) remove(f[q[hh]] + w[q[hh + 1]]);
                hh ++ ;
            }
        }

        while (hh <= tt && w[q[tt]] <= w[i])
        {
    
    
            if (hh < tt) remove(f[q[tt - 1]] + w[q[tt]]);
            tt -- ;
        }
        q[ ++ tt] = i;
        if (hh < tt) S.insert(f[q[tt - 1]] + w[q[tt]]);

        f[i] = f[j - 1] + w[q[hh]];
        if (S.size()) f[i] = min(f[i], *S.begin());
    }

    printf("%lld\n", f[n]);

    return 0;
}

五、AcWing 141. 周期(简单)

题目描述

一个字符串的前缀是从第一个字符开始的连续若干个字符,例如 abaab 共有 5 5 5 个前缀,分别是 aababaabaaabaab
我们希望知道一个 N N N 位字符串 S S S 的前缀是否具有循环节。
换言之,对于每一个从头开始的长度为 i ( i > 1 ) i(i>1) ii>1的前缀,是否由重复出现的子串 A A A 组成,即 A A A … A AAA…A AAAA A A A 重复出现 K K K 次, K > 1 K>1 K>1)。
如果存在,请找出最短的循环节对应的 K K K 值(也就是这个前缀串的所有可能重复节中,最大的 K K K 值)。

输入格式

输入包括多组测试数据,每组测试数据包括两行。
第一行输入字符串 S S S 的长度 N N N
第二行输入字符串 S S S
输入数据以只包括一个 0 0 0 的行作为结尾。

输出格式

对于每组测试数据,第一行输出 Test case # 和测试数据的编号。
接下来的每一行,输出具有循环节的前缀的长度 i i i 和其对应 K K K,中间用一个空格隔开。
前缀长度需要升序排列。
在每组测试数据的最后输出一个空行。

数据范围

2 ≤ N ≤ 1000000 2≤N≤1000000 2N1000000

输入样例

3
aaa
4
abcd
12
aabaabaabaab
0

输出样例

Test case #1
2 2
3 3
(空行)
Test case #2
(空行)
Test case #3
2 2
6 2
9 3
12 4
(空行)

具体实现

1. 实现思路

  • 我们需要去寻找一个循环节,并且这个循环节可以被字符串的长度整除,并且,要尽可能使这个循环节的长度尽可能大。
  • 我们需要先求所有满足情况的 next 数组,然后验证最小循环节是否可以整除 i 即可。
  • while(scanf(“%d”,&n),n) 语句就是先输入一个整数赋值给 n(表达式 1),然后 while 括号里的值是 n(表达式 2),若 n 为真则进行 while 循环,n 为 0 时则不做处理。

2. 实现代码

#include <bits/stdc++.h>
using namespace std;
const int N = 1000010;
int n;
char s[N];
int ne[N];
int main()
{
    
    
    int T=1;
    while(scanf("%d",&n),n)
    {
    
    
        scanf("%s",s+1);
        for(int i=2,j=0;i<=n;i++)
        {
    
    
            while(j&&s[i]!=s[j+1])
			{
    
    
				j=ne[j];
			}
            if(s[i]==s[j+1])
			{
    
    
				j++ ;
			}
            ne[i]=j;
        }
        printf("Test case #%d\n",T);
        T++;
        for(int i=1;i<=n;i++)
        {
    
    
            int t=i-ne[i];
            if(i%t==0&&i/t>1)
            {
    
    
            	printf("%d %d\n",i,i/t);
			}                
        }
        puts("");
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_45891612/article/details/129395608