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


一.连通分量

无向图 G 有 n 个顶点和 m 条边。求 G 的连通分量的数目。

输入格式:

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

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

输出格式:

1行,1个整数,表示所求连通分量的数目。

输入样例:

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

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

输出样例:

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

2

 


解题思路

 首先要了解连通分量的定义。连通分量:就是指子图里面的任意两个分量i和j都存在路径可以让i到j,j到i,而在本题我们讨论的是无向图,若是在有向图中,我们就要注意方向的问题,如果在有向图中依然满足任意两点连通,此时我们称为强连通分量。

总结:强连通分量存在于有向图,子图任意两个分量连通,如果原来不连通的分量,在有向图变成无向图后连通,称为弱连通分量

 接下来我们以该图为例子,讲解思路。


法一

首先,我们可以很明显的发现,本图中存在两个连通分量,那么我们有两种思路,第一种就是我们都熟悉的DFS遍历,你只要是连通的分量,就必然会被遍历到,我们可以随便选取一个点,当进行完一次DFS后,一定意味着一个连通分量被打通了,我们只要用一个count来计数DFS的次数就可以啦,代码如下

#include<iostream>
#include<vector>
using namespace std;
int mark[50001];//标记
vector<int>apex[50001];//常规方法,用vector来建图,本题没有权重,所以直接用int
void dfs(int x) {
	int i;
	mark[x] = 1;
	for (i = 0; i < apex[x].size(); i++) {
		int next = apex[x][i];
		if (mark[next] == 0)
			dfs(next);
	}
}
int main() {
	int n, m,i,u,v;
	int count = 0;
	cin >> n >> m;
	for (i = 1; i <= m; i++) {
		cin >> u >> v;
		apex[u].push_back(v);
		apex[v].push_back(u);
	}//存图,虽然是无向图,但是也要两边都存的,不然就会遍历不了
	for (i = 1; i <= n; i++) {
		if (mark[i] == 0) {
			count++;
			dfs(i);
		}
	}
	cout << count;
	return 0;
}

 

 法二

法二的方法,也是我在考试的时候使用的方法,并查集的操作,相连通的分量就是在并查集中的一个子集,那么我们只要将连通的合并就好啦,当然,虽然本题限制不大,但我们也应当学会使用路径压缩和按秩分配来进行优化,代码如下

#include<iostream>
using namespace std;
int father[50001];//并查集的父亲
int find(int x) {
	if (father[x]==x)return x;
	return father[x] = find(father[x]);
}
void uni(int x, int y) {
	if (find(x) == find(y))
		return;
	father[find(x)] = find(y);
	return;
}
int main() {
	int count = 0;
	int n, m,i,u,v;
	cin >> n >> m;
	for (i = 1; i <= n; i++)
		father[i] = i;
	for (i = 1; i <= m; i++) {
		cin >> u >> v;
		uni(u, v);
	}
	for (i = 1; i <= n; i++) {
		if (father[i] == i)
			count++;
	}
	cout << count;
	return 0;
}

 

 


 二.旅行I

五一要到了,来一场说走就走的旅行吧。当然,要关注旅行费用。由于从事计算机专业,你很容易就收集到一些城市之间的交通方式及相关费用。将所有城市编号为1到n,你出发的城市编号是s。你想知道,到其它城市的最小费用分别是多少。如果可能,你想途中多旅行一些城市,在最小费用情况下,到各个城市的途中最多能经过多少城市。

输入格式:

第1行,3个整数n、m、s,用空格分隔,分别表示城市数、交通方式总数、出发城市编号, 1≤s≤n≤10000, 1≤m≤100000 。

第2到m+1行,每行三个整数u、v和w,用空格分隔,表示城市u和城市v的一种双向交通方式费用为w , 1≤w≤10000。

输出格式:

第1行,若干个整数Pi,用空格分隔,Pi表示s能到达的城市i的最小费用,1≤i≤n,按城市号递增顺序。

第2行,若干个整数Ci,Ci表示在最小费用情况下,s到城市i的最多经过的城市数,1≤i≤n,按城市号递增顺序。

输入样例:

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

5 5 1
1 2 2
1 4 5
2 3 4
3 5 7
4 5 8

输出样例:

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

0 2 6 5 13
0 1 2 1 3

 解题思路

非常常规的一题dijkstra的题目啦,相信你们一定都已经掌握了dijkstra算法,即求某一点到其他点的最短路,这题的特殊之处在于,求在满足到某点为最小路径的同时,经过的城市要最多。事实上,当我们回去思考dijkstra算法的时候,我们会发现,在求最短路径的时候,一定要进行一次松弛操作(就是两边之和大于第三边,我们取第三边才能最小),那么我们就可以用一个数组,存储每个城市最短路上的最多城市,在松弛条件下,进行更新,代码如下

#include<iostream>
#include<vector>
using namespace std;
int city[10001];//city[i]表示在i点的经过城市数量
int mark[10001];//标识城市有没有进入最小集合
int dist[10001];//到某点的最小距离
struct node {
	int next;
	int weight;
};
vector<node>apex[10001];//存储城市边集
void dij(int x,int n) {
	int i;
	for (i = 1; i <= n; i++) {
		dist[i] = 20000;
		mark[i] = 0;
		city[i] = 0;
	}//初始
	dist[x] = 0;
	for (i = 1; i < n; i++) {
		int minest = 100000;
		int v=1,j;
		for (j = 1; j <= n; j++) {
			if (dist[j] < minest&&mark[j]==0) {
				minest = dist[j];
				v = j;
			}
		}//找到最短城市
		mark[v] = 1;
		for (int u = 0; u < apex[v].size(); u++) {
			int cost = apex[v][u].weight;//路径权
			int seer = apex[v][u].next;//路径终点
			
			if (dist[v] + cost < dist[seer]) {
				dist[seer] = dist[v] + cost;
				city[seer] = city[v] + 1;
			}
			if (dist[v] + cost == dist[seer]) {
				if (city[v] + 1 > city[seer])
					city[seer] = city[v] + 1;
			}
		}
	}
}
int main() {
	int n, m, s;
	cin >> n >> m >> s;
	node t;
	for (int i = 1; i <= m; i++) {
		int a, b, c;
		cin >> a >> b >> c;
		t.next = b;
		t.weight = c;
		apex[a].push_back(t);
		t.next = a;
		apex[b].push_back(t);
	}//存储边集
	dij(s, n);
	for (int i = 1; i <= n; i++) {
		if (i == 1)
			cout << dist[i];
		else
		cout << " " << dist[i];
	}
	cout << endl;
	for (int i = 1; i <= n; i++) {
		if (i == 1)
			cout << city[i];
		else
		cout << " " << city[i];
	}
	return 0;
}


 

 三.算术表达式计算

任务: 计算算术表达式的值。

算术表达式按中缀给出,以=号结束,包括+,-,,/四种运算和(、)分隔符。运算数的范围是非负整数,没有正负符号,小于等于109 。

计算过程中,如果出现除数为0的情况,表达式的结果为”NaN” ; 如果中间结果超出32位有符号整型范围,仍按整型计算,不必特殊处理。
输入保证表达式正确。

输入格式:

一行,包括1个算术表达式。算术表达式的长度小于等于1000。

输出格式:

一行,算术表达式的值 。

输入样例:

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

(1+30)/3=

输出样例:

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

10

 解题思路

 常规的表达式计算,相信大家都已经知道大致的思路了,下面我讲讲大致的思路,你可以用c++自带的vector或者stack建立栈(如果你想方便的,最好不要自己建栈),我们分为数字栈和操作符栈,当读入数字的时候,我们将数字压栈,那么当读入操作符的时候,我们就要根据操作符的优先级来进行操作,乘和除大于加和减,括号另作判断。当我们读入左括号时,直接压进去栈就好了,当我们没有读入左括号,那就要分两种情况了,读入正常操作符和右括号,第一种情况,我们要判断优先级,如果读入的操作符优先级小于栈顶,那么栈顶进行计算,否则压入,右括号的话,我们就要不停出栈。相信这么一大段文字你一定很难理解,下面我用上课的ppt来解释

偷个懒,代码如下

#include<iostream>
#include<string>
#include<vector>
using namespace std;
vector<int>number;
vector<char>ope;
int prior(char x) {
	if (x == '+' || x == '-')
		return 1;
	if (x == '*' || x == '/')
		return 2;
		return 0;
}
int ca(int x, char a, int y) {
	if (a == '*')
		return x * y;
	else 
		if (a == '+')
			return x + y;
	else
		if (a == '-') 
			return x - y;
	else {
		if (y == 0) {
			cout << "NaN";
			exit(0);
		}
		return x / y;
	}
}
void cal(string s) {
	int len = s.length();
	s[len - 1] = '\0';
	int i = 0;
	while (i < len - 1) {
		if (s[i] <= '9' && s[i] >= '0') {
			int num = 0;
			while (s[i] <= '9' && s[i] >= '0') {
				num = num * 10 + s[i] - '0';
				i++;
			}
			number.push_back(num);
		}
		else {
			if (s[i] == '(') {
				ope.push_back(s[i]);
				
			}
			else
				if (s[i] != ')') {
					while (!ope.empty() && prior(ope.back()) >= prior(s[i])) {
						int a, b;
						char ch;
						a = number.back();
						number.pop_back();
						b = number.back();
						number.pop_back();
						ch = ope.back();
						ope.pop_back();
						number.push_back(ca(b, ch, a));
					}
					ope.push_back(s[i]);
				
				}
				else {
					while (ope.back() != '(') {
						int a, b;
						char ch;
						a = number.back();
						number.pop_back();
						b = number.back();
						number.pop_back();
						ch = ope.back();
						ope.pop_back();
						number.push_back(ca(b, ch, a));
					}
					ope.pop_back();
					
				}
			i++;
		}
	}
	while (!ope.empty()) {
		int a = number.back();
		number.pop_back();
		int b = number.back();
		number.pop_back();
		char ch = ope.back();
		ope.pop_back();
		number.push_back(ca(b, ch, a));
	}
	cout << number.back();
}
int main() {
	string s;
	cin >> s;
	cal(s);
	return 0;
}

 

四.序列乘积

两个递增序列A和B,长度都是n。令 Ai 和 Bj 做乘积,1≤i,j≤n.请输出n*n个乘积中从小到大的前n个。

输入格式:

第1行,1个整数n,表示序列的长度, 1≤n≤100000.

第2行,n个整数Ai,用空格分隔,表示序列A,1≤Ai≤40000,1≤i≤n.

第3行,n个整数Bi,用空格分隔,表示序列B,1≤Bi≤40000,1≤i≤n.

输出格式:

1行,n个整数,用空格分隔,表示序列乘积中的从小到大前n个。

输入样例:

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

5
1 3 5 7 9 
2 4 6 8 10

输出样例:

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

2 4 6 6 8

 吐槽

 考试的时候,因为时间不太够了,所以这题我就用暴力的办法了,最后拿了60分哈哈感觉不是很亏,下面给出暴力的思路。我们先假定一个长度为n的序列已经是最小的了,即a[0]*b[i](1<=i<=n),接下来用一个二重循环计算,用插入排序的思想,把数字插入,毫无疑问的会爆炸,代码如下

#include<iostream>
#include<cstdio>
using namespace std;
int a[100001], b[100001],c[100001];
int main()
{
	int n,tt=0;
	scanf("%d", &n);
	for (int i = 0; i < n; i++) 
		scanf("%d", &a[i]);
	for (int i = 0; i < n; i++) 
		scanf("%d", &b[i]);

	for (int i = 0; i < n; i++)
		c[tt++] = a[0] * b[i];//先把原来的n个绝对最小的数字存进
	for (int i = 1; i < n; i++)
	{
		for (int j= 0; j < n; j++)
		{
			int temp = 0;
			temp = a[i] * b[j];
			int end = n - 2;
			int judge = 0;
			 while(temp < c[end+1])
			{
				 c[end + 2] = c[end+1];
				 end--;
				 judge = 1;
			}
			 if (judge)
				 c[end + 2] = temp;
            if(judge==0&&j==0)
                break;
		}
	}
e:
	for (int i = 0; i < n; i++)
		if (i == 0) printf("%d", c[i]);
		else printf(" %d", c[i]);
	printf("\n");
	return 0;
}


 正经的解题思路

 a数组与b数组,我们先假设a数组的第一个数字乘b数组全部数字所得到的n个数字是目前的最小序列,然后再利用优先队列的思想进行排序,时间复杂度为nlogn。我们排序出一个矩阵,这个矩阵就是a数组乘b数组所得的n*n的矩阵,第一列就是我们计算出的目前假设最小序列,我们从中取出最小值,然后在最小值所处在的行取下一个值进去优先队列,如下图

 代码如下

#include<cstdio>
#include<iostream>
#include<queue>
using namespace std;
long long a[100001], b[100001], c[100001];
struct node {
	int row, j; 
	long long weight;
};

bool operator <(const node& a, const node& b) {
	return a.weight > b.weight;
}
priority_queue<node> apex;
int main() {
	int n, i;
	node z;
	scanf("%d", &n);
	for (i = 0; i < n; ++i) {
		scanf("%lld", &a[i]);
	}
	for (i = 0; i < n; ++i) {
		scanf("%lld", &b[i]);
	}
	for (i = 0; i < n; ++i)
	{
		z.row = 0;
		z.j = i;
		z.weight = a[0] * b[i];
		apex.push(z);
	}
	for (i = 0; i < n; i++) {
		z = apex.top();
		apex.pop();
		c[i] = z.weight;
		z.row++;
		z.weight = a[z.row] * b[z.j];
		apex.push(z);
	}
	for (i = 0; i < n; i++) {
		if (i != n - 1)
			printf("%lld ", c[i]);
		else
			printf("%lld", c[i]);
	}
	return 0;
	}

 

 

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值