JLU(2023)第二次数据结构上机考

第一题 图的深度优先搜索I

无向图 G 有 n 个顶点和 m 条边。求图G的深度优先搜索树(森林)以及每个顶点的发现时间和完成时间。每个连通分量从编号最小的结点开始搜索,邻接顶点选择顺序遵循边的输入顺序。

在搜索过程中,第一次遇到一个结点,称该结点被发现;一个结点的所有邻接结点都搜索完,该结点的搜索被完成。深度优先搜索维护一个时钟,时钟从0开始计数,结点被搜索发现或完成时,时钟计数增1,然后为当前结点盖上时间戳。一个结点被搜索发现和完成的时间戳分别称为该结点的发现时间和完成时间

输入格式:

第1行,2个整数n和m,用空格分隔,分别表示顶点数和边数, 1≤n≤50000, 1≤m≤100000.

第2到m+1行,每行两个整数u和v,用空格分隔,表示顶点u到顶点v有一条边,u和v是顶点编号,1≤u,v≤n.

输出格式:

第1到n行,每行两个整数di和fi,用空格分隔,表示第i个顶点的发现时间和完成时间1≤i≤n 。

第n+1行,1个整数 k ,表示图的深度优先搜索树(森林)的边数。

第n+2到n+k+1行,每行两个整数u和v,表示深度优先搜索树(森林)的一条边<u,v>,边的输出顺序按 v 结点编号从小到大。

输入样例:

在这里给出一组输入。例如:

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

输出样例:

在这里给出相应的输出。例如:

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

 


解题思路

 从题目中就可以看出,本题要使用DFS进行解答,与常规的DFS的区别之处在于多了一个时间戳,而且我们发现,假如路线是一个圆圈,最晚访问到的节点,就是开始时间最晚的节点,完成时间最早,这就很符合我们对递归函数的理解,一层一层的进入,再从最上层一层一层的离开(先进后出),这部分就是这么想的,然后题目还要求我们写出有几条边,加一个全局变量,递归一次加一即可,在本题,最重要的是按节点从小到大的顺序输出边,如果你用排序的算法,会超时(考试试过),所以,我们就要用一个path数组来存储某点的上一个点,最后再依次输出即可,代码如下

#include<vector>
#include<algorithm>
#include<iostream>
#include<cstdio>
using namespace std;
vector<int>apex[50001];
int top=0;
int start[50001];
int last[50001];
int coun=0;
int path[50001];
void dfs(int n)
{
	start[n] = ++top;
	for (int i = 0; i < apex[n].size(); i++)
	{
		if (start[apex[n][i]] == 0)
		{
			coun++;
			path[apex[n][i]] = n;
			dfs(apex[n][i]);
		}
	}
	last[n] = ++top;
	return;
}

int main()
{
	int n, m,i;
	scanf("%d%d", &n, &m);
	for (i = 1; i <= m; i++)
	{
		int u, v;
		scanf("%d%d", &u, &v);
		apex[u].push_back(v);
		apex[v].push_back(u);
	}
	for (i = 1; i <= n; i++)
	{
		if (start[i] == 0)
			dfs(i);
	}
	for (i = 1; i <= n; i++)
	{
		printf("%d %d\n", start[i], last[i]);
	}
	printf("%d\n", coun);
	for (i = 1; i <= n; i++)
	{
		if (path[i])
		{
			if (i != n)
				printf("%d %d\n", path[i], i);
			else
				printf("%d %d", path[n], i);
		}
	}
	return 0;
}

第二题二叉树最短路径长度

给定一棵二叉树T,每个结点赋一个权值。计算从根结点到所有结点的最短路径长度。路径长度定义为:路径上的每个顶点的权值和。

输入格式:

第1行,1个整数n,表示二叉树T的结点数,结点编号1..n,1≤n≤20000。

第2行,n个整数,空格分隔,表示T的先根序列,序列中结点用编号表示。

第3行,n个整数,空格分隔,表示T的中根序列,序列中结点用编号表示。

第4行,n个整数Wi,空格分隔,表示T中结点的权值,-10000≤Wi≤10000,1≤i≤n。

输出格式:

1行,n个整数,表示根结点到其它所有结点的最短路径长度。

输入样例:

在这里给出一组输入。例如:

4
1 2 4 3
4 2 1 3
1 -1 2 3

输出样例:

在这里给出相应的输出。例如:

1 0 3 3

解题思路 

 本题考验的是能否快速写出根据前序遍历和中序遍历确定一颗二叉树的能力,模板题(虽然我没过两个点),这里我们使用递归来建树,众所周知,前序遍历的第一个点,就是二叉树的根节点,当我们在中序遍历中找到该点后,由中序遍历的定义我们可知,中序遍历的起始点到该点都是二叉树的左子树(除了被确认的点),该点到末尾都是二叉树的右子树,这种问题可以化为子问题逐级求解,所以可以用递归进行建树,代码如下

#include<iostream>
using namespace std;
int sma[20010];
int preorder[20010];
int inorder[20010];
struct tree {
	int num;
	int weight;
	tree* left;
	tree* right;
};
tree* binary(int pres, int prel, int ins, int inl) {
    if(pres>prel||ins>inl)
        return NULL;
    else{
	int jiedian = preorder[pres];//先根遍历第一个节点
	tree* root = new tree;
        root->num=jiedian;
        root->weight=sma[jiedian];
        root->left=NULL;
        root->right=NULL;
	int i;
	for (i = ins; i <= inl; i++) {
		if (inorder[i] == jiedian)
			break;
	}
		root->left = binary(pres + 1, pres + i - ins, ins, i - 1);
		root->right = binary(pres+i-ins+1, prel, i + 1, inl);
	return root;
    }
}
void cal(tree* root, int now) {
	if (root == NULL)
		return;
	int n = root->num;
	int u = root->weight;
	sma[n] = u + now;
	cal(root->left, sma[n]);
	cal(root->right, sma[n]);
	return;
}
int main() {
	int n, i;
	cin >> n;
	for (i = 1; i <= n; i++)
		cin >> preorder[i];
	for (i = 1; i <= n; i++)
		cin >> inorder[i];
	for (i = 1; i <= n; i++)
		cin >> sma[i];
	tree* root = binary(1, n, 1, n);
	cal(root, 0);
	for (i = 1; i < n; i++)
		printf("%d ", sma[i]);
	printf("%d", sma[n]);
	return 0;
}

 沉痛反思

本题给我卡爆,以至于我后面三题看都没看一眼,后面经过十分长时间的检查,才发现是一个变量在调用函数时写错了,ins写成了inl,愣是没有检查出来,在这种情况下居然还过了三个点。所以写变量的时候尽量写全一点,有时候就一个小字母的区别在检查时根本看不出来


第三题 供电

地区或者建一个供电站,或者修一条线道连接到其它有电的地区。试确定给N个地区都供上电的最小费用。

输入格式:

第1行,两个个整数 N 和 M , 用空格分隔,分别表示地区数和修线路的方案数,1≤N≤10000,0≤M≤50000。

第2行,包含N个用空格分隔的整数P[i],表示在第i个地区建一个供电站的代价,1 ≤P[i]≤ 100,000,1≤i≤N 。

接下来M行,每行3个整数a、b和c,用空格分隔,表示在地区a和b之间修一条线路的代价为c,1 ≤ c ≤ 100,000,1≤a,b≤N 。

输出格式:

一行,包含一个整数, 表示所求最小代价。

输入样例:

在这里给出一组输入。例如:

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

输出样例:

在这里给出相应的输出。例如:

9


解题思路

 事实上就是求解最小生成树,一开始必须找到一个建电站代价最小的点,然后进行更新,接下来就是边权和点权的比较,如果边的代价小于点,那么该点建电站的代价更新,然后利用prim算法,将每次代价最小的加入,代码如下

#include<iostream>
#include<vector>
using namespace std;
int cost[100000];
struct city {
	int to;
	int costcity;
};
int mark[10001];
vector<city>apex[50001];
int main() {
	int n, m;
	cin >> n >> m;
	int i, k;
	for (i = 1; i <= n; i++)
		cin >> cost[i];
	for (i = 1; i <= m; i++) {
		city a, b;
		int u, v;
		cin >> u >> v >> a.costcity;
		a.to = v;
		b.to = u;
		b.costcity = a.costcity;
		apex[u].push_back(a);
		apex[v].push_back(b);
	}
	int s, sum = 0,j;
	for (j = 1; j <= n; j++) {
		int max = 100001;
		for ( i= 1; i <= n; i++) {
			if (cost[i] < max && mark[i] == 0) {
				max = cost[i];
				s = i;
			}
		}
		mark[s] = 1;
		for (k = 0; k < apex[s].size(); k++) {
			int tt = apex[s][k].to;
			if (apex[s][k].costcity < cost[tt] && mark[tt] == 0)
				cost[tt] = apex[s][k].costcity;
		}
	}
	for (i = 1; i <= n; i++)
		sum += cost[i];
	cout << sum;
	return 0;
}

 


第四题 幸福指数

人生中哪段时间最幸福?幸福指数可能会帮你发现。幸福指数要求:对自己每天的生活赋予一个幸福值,幸福值越大表示越幸福。一段时间的幸福指数就是:这段时间的幸福值的和乘以这段时间的幸福值的最小值。幸福指数最大的那段时间,可能就是人生中最幸福的时光。

输入格式:

第1行,1个整数n,, 1≤n≤100000,表示要考察的天数。

第2行,n个整数Hi,用空格分隔,Hi表示第i天的幸福值,0≤n≤1000000。

输出格式:

第1行,1个整数,表示最大幸福指数。

第2行,2个整数l和r,用空格分隔,表示最大幸福指数对应的区间[l,r]。如果有多个这样的区间,输出最长最左区间。

输入样例:

在这里给出一组输入。例如:

7
6 4 5 1 4 5 6

输出样例:

在这里给出相应的输出。例如:

60
1 3

解题思路 

 单调栈,如果一个数是该区间的最小数字,那么我们就要向左向右找到这个区间,就是找到比它小的数字,确定区间,那么就可以用一个单调递增的栈,该数字入栈前要与栈顶比较大小,大就直接入栈,区间确定,小就让栈顶出栈知道栈顶小于该数字,区间确定,左边和右边各来一次单调栈就可以确定一个数字的左右区间,代码如下

#include<iostream>
#include<stack>
using namespace std;
stack<int>b;
int righ[100010],lef[100010];
long long sum[100010];
int a[100010], s[100010];
int main() {
    int n, i;
    cin >> n;
    for (i = 1; i <= n; i++) {
        cin >> a[i];
        sum[i] = a[i] + sum[i - 1];
    }
    for (i = 1; i <= n; i++) {
        while (!b.empty() && a[b.top()] >= a[i])
            b.pop();
        if (b.empty()) {
            lef[i] = 1;
        }
        else {
            lef[i] = b.top() + 1;
        }
        b.push(i);
    }
    while (!b.empty())
        b.pop();
    for (i = n; i >=1; i--) {
        while (!b.empty() && a[b.top()] >= a[i])
            b.pop();
        if (b.empty()) {
            righ[i] = n;
        }
        else {
            righ[i] = b.top() - 1;
        }
        b.push(i);
    }
    long long  maxx = -10000000, num;
    int ll = 0, rr = 0;
    for (i = 1; i <= n; i++) {
        num = (sum[righ[i]] - sum[lef[i]-1]) * a[i];
        if (num > maxx) {
            maxx = num; ll = lef[i];  rr = righ[i];
        }
        if (num == maxx && righ[i]-lef[i] > rr - ll) {
            ll = lef[i];  rr = righ[i];
        }
        if (num == maxx && righ[i]-lef[i] == rr - ll) {
            if (lef[i] < ll) {
                ll = lef[i];  rr = righ[i];
            }
        }
    }
    printf("%lld\n%d %d", maxx, ll, rr);
 
}

时间复杂度O(N)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值