5 真二叉树重构(Proper Rebuild)
分数 100
全屏浏览题目
切换布局
作者 upcdsa
单位 中国石油大学(华东)
一般来说,给定二叉树的先序遍历序列和后序遍历序列,并不能唯一确定该二叉树。
(图一)
比如图一中的两棵二叉树,虽然它们是不同二叉树,但是它们的先序、后序遍历序列都是相同的。
但是对于“真二叉树”(每个内部节点都有两个孩子的二叉树),给定它的先序、后序遍历序列足以完全确定它的结构。
将二叉树的n个节点用[1, n]内的整数进行编号,输入一棵真二叉树的先序、后序遍历序列,请输出它的中序遍历序列。
输入格式:
第一行为一个整数n,即二叉树中节点的个数。
第二、三行为已知的先序、后序遍历序列。
要求:
1 ≤ n ≤ 4,000,000
输入的序列是{1,2...n}的排列,且对应于一棵合法的真二叉树
输出格式:
仅一行,给定真二叉树的中序遍历序列。
输入样例:
5
1 2 4 5 3
4 5 2 3 1
输出样例:
4 2 5 1 3
代码长度限制
16 KB
时间限制
400 ms
内存限制
64 MB
#include<bits/stdc++.h>
//载入万能头
#define Max 4000000+10
//定义最大数域
using namespace std;
int pre[Max],post[Max];//定义前序和后序数组
int i,j,k,m,n,e;//定义后边用到的全局变量
//定义一个树的结构体,内含左右子树
struct tree
{
int data;
tree *lt,*rt;//左子树和右子树
};
tree *Rebuild(int pre_lo,int pre_hi,int post_lo,int post_hi) //传入前序和后序递归范围
{
tree *root=new tree;//创建新节点
root->data=pre[pre_lo];//以新节点为根,数据域为前序最左端的数据
root->lt=root->rt=NULL;//其左右子树初始化为空
int pos,num_left;//定义前序左子树根在后序中的位置,左子树节点数
if(post_lo==post_hi)
{
return root;//若后序范围长度为0,返回当前根节点
}
for(i=post_lo; i<=post_hi; i++) //遍历给定范围的后序
if(post[i]==pre[pre_lo+1]) //若前序最左端+1即当前左子树根节点等于后序节点
{
pos=i;//确定该原子在后序中的位置
break;//记录左子树根节点在后序中的位置并退出循环
}
num_left=pos-post_lo + 1;//计算左子树节点数
root->lt=Rebuild(pre_lo + 1,pre_lo + num_left,post_lo,pos);//递归地在左子树中寻找当前节点的左孩子
root->rt=Rebuild(pre_lo + num_left + 1,pre_hi,pos + 1,post_hi - 1);//递归地在右子树中寻找当前节点的右孩子
return root;
}
void Treeprint(tree *root)
{
if(!root)
{
return;
}
Treeprint(root->lt);
printf("%d ",root->data);
Treeprint(root->rt);
}
int main()
{
scanf("%d",&n);
for(i=0; i<n; i++)
{
scanf("%d",&pre[i]);//输入前序序列
}
for(i=0; i<n; i++)
{
scanf("%d",&post[i]);//输入后序序列
}
tree *r=Rebuild(0,n-1,0,n-1);//构建该树的中序序列
Treeprint(r);//输出
return 0;
}
//本题的逻辑结构:二叉树
//本题的存储结构:链式储存
/*使用递归和分而治之的思想
前序中第1个节点为当前的树根,
以样例为例,1为树根,2为第1个左子树根,
在后序中找到对应的节点2,则它之前的节点4和5为左子树的节点,
算上左子树根,此时左子树的节点数为3。
则在先序中,2,4,5为左子树中的节点。
根据分而治之的思想,若求当前树根的左孩子,
可递归地在前序2,4,5,后序4,5,2的这两个范围内搜索,
若求当前树根的右孩子,可递归地在前序3,后序3的这两个范围内搜索,
每次递归的操作与之前一样,找到当前树根,确定当前树的左子树根,
确定该左子树的根(节点)在后序中的位置,划分左子树和右子树节点在序列中的范围,
递归地在左(右)子树中寻找当前节点的左(右)孩子
*/
//效率:
//时间复杂度:O(n)
//空间复杂度:S(n) = O(n)
//测试数据:(1)输入:
/*
5
1 2 4 5 3
4 5 2 3 1
*/
// (2)输出:
/*
4 2 5 1 3
*/
6 旅行商(TSP)
分数 100
全屏浏览题目
切换布局
作者 upcdsa
单位 中国石油大学(华东)
Shrek是一个大山里的邮递员,每天负责给所在地区的n个村庄派发信件。但杯具的是,由于道路狭窄,年久失修,村庄间的道路都只能单向通过,甚至有些村庄无法从任意一个村庄到达。这样我们只能希望尽可能多的村庄可以收到投递的信件。
Shrek希望知道如何选定一个村庄A作为起点(我们将他空投到该村庄),依次经过尽可能多的村庄,路途中的每个村庄都经过仅一次,最终到达终点村庄B,完成整个送信过程。这个任务交给你来完成。
输入格式:
第一行包括两个整数n,m,分别表示村庄的个数以及可以通行的道路的数目。
以下共m行,每行用两个整数v1和v2表示一条道路,两个整数分别为道路连接的村庄号,道路的方向为从v1至v2,n个村庄编号为[1, n]。
要求:
1 ≤ n ≤ 1,000,000
0 ≤ m ≤ 1,000,000
输入保证道路之间没有形成环
输出格式:
输出一个数字,表示符合条件的最长道路经过的村庄数。
输入样例:
4 3
1 4
2 4
4 3
输出样例:
在这里给出相应的输出。例如:
3
代码长度限制
16 KB
时间限制
400 ms
内存限制
64 MB
#include<bits/stdc++.h>
//载入万能头
#define N 100000+10
using namespace std;
int idx;//边的编号
int h[N];//边的头节点
int e[N];//边的终点
int ne[N];//下一条邻边
int q[N];//定义用以模拟队列的数组
int d[N];//d[i]表示顶点i的入度
int cunzi[N];//村子个数
int maxcunzi = 1;//最大通联的村子数,即最后输出的答案
int n,m;//录入的村子数和道路数
void TSP()
{
int hh = 0, tt = -1;
//0入度边入队
for(int i = 1; i <= n; i++)
{
if(!d[i]) q[++tt] = i;
}
//初始化每个顶点目前的村庄个数
for(int i = 1; i <= n; i++)
{
cunzi[i] = 1;
}
//遍历入度为0的点,直到队列为空
while(hh <= tt)
{
int t = q[hh++];
//遍历顶点t 的所有邻接点j
for(int i = h[t]; i != - 1; i = ne[i])
{
int j = e[i];
if(cunzi[t] + 1 > cunzi[j])
{
cunzi[j] = cunzi[t] + 1;
}
maxcunzi = max(maxcunzi, cunzi[j]);
d[j] --;
if(!d[j]) q[++tt] = j;
}
}
}
int main()
{
scanf("%d %d", &n, &m);
memset(h, -1, sizeof(h));//初始化边的头节点,初始值为-1
for(int i = 0;i<m;i++ )//输入道路信息
{
int v1, v2;
scanf("%d %d", &v1, &v2);//输入每条道理的起始点、终点
e[idx] = v2;//边的终点
ne[idx] = h[v1];//下一条邻边
h[v1] = idx ++;//头节点
d[v2]++;//该条路的入度+1
}
TSP();//数据处理
printf("%d\n", maxcunzi);
return 0;
}
//本题的逻辑结构:图-拓扑排序
//本题的存储结构:线性储存
/*因为是单向通行的,即求图中最长的一条路径,要求包含的结点个数最多。
最长路径有个特点就是,起点入度为0,终点出度为0,输入保证道路之间没有形成环
因为如果不是的话,我们向外扩展一个结点,都会得到更长的一条路径。
故可以按照拓扑排序的思想:
用队列(或者其他任何一个集合)维护当前图中入度为0的结点,
每次取出一个结点,根据当前结点更新所有邻接点的路径长度,
而后将当前结点删除。删除后,更新其每一个邻接点的入度,
如果邻接点入度变成0,邻接点入队,直到队列为空,
得到最长的路径所包含的结点数量。
*/
//效率:
//时间复杂度:O(n²)
//空间复杂度:S(n) = O(n)
//测试数据:(1)输入:
/*
4 3
1 4
2 4
4 3
*/
// (2)输出:
/*
3
*/
7 无线广播(Broadcast)
分数 100
全屏浏览题目
切换布局
作者 upcdsa
单位 中国石油大学(华东)
某广播公司要在一个地区架设无线广播发射装置。该地区共有n个小镇,每个小镇都要安装一台发射机并播放各自的节目。
不过,该公司只获得了FM104.2和FM98.6两个波段的授权,而使用同一波段的发射机会互相干扰。已知每台发射机的信号覆盖范围是以它为圆心,20km为半径的圆形区域,因此,如果距离小于20km的两个小镇使用同样的波段,那么它们就会由于波段干扰而无法正常收听节目。现在给出这些距离小于20km的小镇列表,试判断该公司能否使得整个地区的居民正常听到广播节目。
输入格式:
第一行为两个整数n,m,分别为小镇的个数以及接下来小于20km的小镇对的数目。 接下来的m行,每行2个整数,表示两个小镇的距离小于20km(编号从1开始)。
要求:
1 ≤ n ≤ 10000
1 ≤ m ≤ 30000
不需要考虑给定的20km小镇列表的空间特性,比如是否满足三角不等式,是否利用传递性可以推出更多的信息等等。
输出格式:
如果能够满足要求,输出1,否则输出-1。
输入样例:
4 3
1 2
1 3
2 4
输出样例:
1
代码长度限制
16 KB
时间限制
400 ms
内存限制
64 MB
#include<bits/stdc++.h>
//载入万能头
#define MAX 10000+10//定义小镇数量最大值
using namespace std;
int n, m; //小镇数-相距20Km内小镇对数
int que[MAX]; //模拟队列
int head,tail;//-队首-队尾
int cover; //Broadcast放置数量
//邻接小镇的结构体
struct Node{
int num;
Node *next;
Node(){ next = NULL; }
Node(int n, Node *nn) :num(n), next(nn){}//定义Node的用法
};
//小镇的结构体
struct Town{
int state; //状态
Node *nt; //下一个小镇
Town(){ state = 0; nt = NULL;}
void insert(int num);//操作函数
}town[MAX];
//加入小镇对,即边的输入
void Town::insert(int num)
{
if (this->nt == NULL)
{
this->nt = new Node(num,NULL);//该小镇没有下一个邻接的小镇,即把nt设为空
}
else
{
this->nt = new Node(num,this->nt);//有邻接小镇的就加到nt中
}
}
//广度搜索BFS
bool BFS(int x)
{
que[tail++] = x;//对应位置队列的值=所在位置序号
town[x].state = 1;//默认没有干扰
cover++; //No.x被cover
while (head < tail)//搜索到最后一层
{
Town cur = town[que[head]]; //当前小镇
Node *tmp = cur.nt; //指向nextTown
while (tmp != NULL)
{
if (!town[tmp->num].state){
town[tmp->num].state = -cur.state; //cover不同Broadcast
cover++; //No.(tmp->num)被cover
que[tail++] = tmp->num;
}
else if (town[tmp->num].state == cur.state) //被干扰
return false;
tmp = tmp->next;
}
head++;
}
return true;
}
int main()
{
scanf("%d %d", &n, &m);
for (int i = 0; i < m; i++)
{
int x, y;
scanf("%d %d", &x, &y); //输入的小镇间距离默认是<20Km的
town[x].insert(y);//输入边x端点
town[y].insert(x);//输入边y端点
}
for (int i = 1; i <= n; i++)
{
if (!town[i].state)
{
if (BFS(i) == false) //调用BFS-信号被干扰
{
printf("-1\n");
return 0;
}
if (cover == n) //一一对应没有矛盾的即可覆盖
{
printf("1\n");
}
}
}
return 0;
}
//本题的逻辑结构:图-广度遍历
//本题的存储结构:链式储存
//用1标记源点,-1标记邻接点,1标记邻接点的邻接点
/*
将小镇模拟为一对无向边结点,生成一个多连通无向图,原问题要求这一对小镇不能够放置同种频率的波段,
所以可以将问题比对树的层次遍历问题——父结点和子结点的数据不能相同,
可化为图的广度优先搜索问题——结点和其邻接点的数据不能相同
(利用BFS一层一层向外拓展并标记,找到一对邻接点数据相同则返回false,全部标记成功则返回true)。
将任意一点作为源点入队(标记为1),向外将其所有邻接点入队(标记为-1),
再将源点出队,再取队首点所有邻接点入队(标记为1),
此判断有无邻接点为与队首同标记,有则返回false,没有则继续
直至遍历所有小镇
*/
//效率:
//时间复杂度:O(n²)
//空间复杂度:S(n) = O(n)
//测试数据:(1)输入:
/*
4 3
1 2
1 3
2 4
*/
// (2)输出:
/*
1
*/
8 传染链( Infectious Chain )
分数 100
全屏浏览题目
切换布局
作者 upcdsa
单位 中国石油大学(华东)
某病毒可以人传人,且传染能力极强,只要与已感染该病毒的人发生接触即刻感染。
现给定一些感染该病毒的人员接触关系,要求你找出其中最早最长的一条传染链。
输入格式:
输入在第一行中给出一个正整数 N(N≤10^4),即感染病毒的人员总数,从 0 到 N−1 进行编号。
随后N 行按照编号顺序给出人员接触信息,每行按以下格式描述该人员的接触对象:
k 接触人员1 …… 接触人员k
其中 k 是该编号人员接触的其他人总数,后面按照时间先后给出所有接触的人员编号。题目保证传染源头有且仅有一个,且已被感染人员不会与另一个感染人员再接触。
输出格式:
第一行输出从源头开始的最早最长传染链长度。
第二行输出从源头开始的最早最长传染链,编号之间以1个空格分隔,行首尾不得有多余空格。这里的最早最长传染链是指从源头开始的传染链上的人数最多,且被感染的时间最早。
所谓时间最早指的两个长度相等的传染链{a1,a2,…,an}和{b1,b2,…,bn},存在1≤k<n,对于所有的i (1≤i<k)都满足ai=bi,且ak被感染的时间早于bk被感染的时间。
输入样例:
10
0
3 3 4 7
2 1 9
1 6
1 5
0
0
0
2 6 0
1 8
输出样例:
4
2 1 3 6
代码长度限制
16 KB
时间限制
400 ms
内存限制
64 MB
#include<bits/stdc++.h>
//载入万能头
#define MAX 10000+10//确定感染病毒人数的最大值
using namespace std;
vector<int> v[MAX];//使用容器记录人员接触信息
int step[MAX];//step[i]表示从人员i开始可以传染的最长的链的长度
int nxt[MAX];//最终的nxt[i]将表示最长最早的传染链上人员i传染的下一个人员
int ans=0;//最终的传染链长度
//获取从人员index开始的最长最早的传染链长度
int DFS(int index)
{
//人员index没有接触其他人时,从它开始的传染链则为他自己,长度为1
if(v[index].size()==0)
{
return 1;
}
int steps=0;//将作为返回值,表示从index开始的最早最长的传染链长度
for(int i = 0;i < v[index].size();i++)//遍历人员index接触过的人
{
//v[index][i]指人员index接触的第i个人的编号,假设它为k
int k=v[index][i];
//step[k]即表示从人员k开始的最早最长的传染链长度
//如果这个长度加上它走到k这一步大于现在记录的最长的长度steps,则进行更新,step[k]等于0时前面的大于情况也满足,需要排除
if(step[k]+1 > steps && step[k]!=0)
{
//从index开始走到人员k只需要走1步,
//从人员k开始的最长的传染链长度为step[k],
//所以从index开始最长的传染链长度就为step[k]+1
steps=step[k]+1;
nxt[index]=k;//目前找到的最长最早传染链中index接触的下一个人为k
}
else if(step[k]==0)//如果还不知道从k开始的传染链长度
{
//获取从k开始的最长的传染链长度DFS(k)
//然后加上从index走到k这个位置的1步
int temp=DFS(k)+1;
if(temp>steps)//和上一个if的判断方法一致
{
steps=temp;
nxt[index]=k;
}
}
}
step[index] = steps;//记录从index开始最早最长的传染链的长度
return steps;
}
int main()
{
int n;
scanf("%d",&n);//输入总人数
memset(v,0,sizeof(v));//初始化
memset(step,0,sizeof(step));//初始化
memset(nxt,0,sizeof(nxt));//初始化
for(int i=0;i<n;i++)//存储所有的传染信息
{
int k;
scanf("%d",&k);//人员i接触的人数
for(int j=0;j<k;j++)//把接触到的每个人都存入动态数组
{
int t;
scanf("%d",&t);
v[i].push_back(t);
}
}
int index=0;
for(int i=0;i<n;i++)//遍历每一条人员接触信息
{
int temp=DFS(i);//从人员i开始的最长的那条传染链的长度
//传染链长度大于上一次记录的结果是记录该条链的起始点并更新最大长度
if(temp>ans)
{
index=i;//起始点
ans=temp;
}
}
printf("%d\n",ans);
for(int i=0;i<ans-1;i++)
{
printf("%d ",index);
index=nxt[index];
}
printf("%d",index);
}
//本题的逻辑结构:图-广度遍历
//本题的存储结构:链式储存
//用1标记源点,-1标记邻接点,1标记邻接点的邻接点
/*
1、要求找最长的路径,考虑用dfs
2、因为每个人感染的人数不同,所以采用动态数组会比较方便,
当然也可以用数组来表示,只需要给不需要的空间一个如(-1)这样的标识即可。
3、将所有的动态数组存放在一个数组里面,这样通过下标就可以访问每一个人先后接触的有哪些人
4、在dfs里面也可以用动态规划的思想来对时间进行优化,用一个step[] 数组记录从某一个位置开始能走的最大步数,
后面出现这个数字时便可直接调用,避免重复计算。比如在上面图中的案例中,一开始已经得知了从3开始最深是2,
那后面如果又走到了3,如果已经记录下来了,那直接便可以加上2,而不用重复走一遍3->6
5、用一个nxt数组找出从某一个位置开始找到可以走的最大的步数时进行记录,
这样就可以直接对最长路径进行输出,和上一条思路一致,不再赘述,都是为了避免重复计算
*/
//效率:
//时间复杂度:O(n²)
//空间复杂度:S(n) = O(n)
//测试数据:(1)输入:
/*
10
0
3 3 4 7
2 1 9
1 6
1 5
0
0
0
2 6 0
1 8
*/
// (2)输出:
/*
4
2 1 3 6
*/