> 文章列表 > XMU 算法分析与设计第三次上机题解

XMU 算法分析与设计第三次上机题解

XMU 算法分析与设计第三次上机题解

文章目录

  • 一、BFS试炼之微博转发
  • 二、DFS试炼之不同路径数
  • 三、并查集试炼之合并集合
    • 并查集的介绍
  • 四、堆排序
    • 堆排序的介绍
  • 五、厦大GPA(分组背包
    • 分组背包介绍
  • 六、消防安全指挥问题(最短路Floyd)
  • 七、铺设光纤问题(最小生成树Prim)
  • 八、CCF-A会报告(多重背包问题)
    • 多重背包介绍


在这里插入图片描述

一、BFS试炼之微博转发

BFS试炼之微博转发

描述

微博上的用户既可能有很多关注者,也可能关注很多其他用户。

因此,形成了一种基于这些关注关系的社交网络。

当用户在微博上发布帖子时,他/她的所有关注者都可以查看并转发他/她的帖子,然后这些人的关注者可以对内容再次转发…

现在给定一个社交网络,假设只考虑L层关注者,请你计算某些用户的帖子的最大可能转发量。

补充

如果B是A的关注者,C是B的关注者,那么AA的第一层关注者是B,第二层关注者是C。

输入

第一行包含两个整数,N表示用户数量,LL示需要考虑的关注者的层数。

假设,所有的用户的编号为1∼N。

接下来N行,每行包含一个用户的关注信息,格式如下:

M[i] user_list[i]

M[i]是第ii名用户关注的总人数,user_list[i]是第ii名用户关注的M[i]个用户的编号列表。

最后一行首先包含一个整数K,表示询问次数,然后包含K个用户编号,表示询问这些人的帖子的最大可能转发量。

数据范围

1≤N≤1000,
1≤L≤6,
1≤M[i]≤100,
1≤K≤N

输出

按顺序,每行输出一个被询问人的帖子最大可能转发量。

假设每名用户初次看到帖子时,都会转发帖子,只考虑L层关注者。

输入样例 1

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

输出样例 1

4
5

解题思路:

  • 可以转变为,求解一张有向图中,到指定点的距离 ≤ \\leq L的点的个数。即用BFS来遍历图
  • 若A关注了B,则B的帖子可以抵达A,A可以转发B的帖子。创建<B,A>边
  • 用链式前向星创建图

代码实现:

#include<iostream>
#include<queue>
#include<string.h>
#define MaxSize 1050
using namespace std;
int N, L, K;//无权值的链式前向星
typedef struct Edge {int to,next;
} Edge;
int tot;
int head[MaxSize];//head[i],第i个点的第一个孩子结点的索引
Edge edge[MaxSize];//加边函数
void add_edge(int u, int v) { //u为起点,v为终点,w为权值edge[tot].to = v; //tot为空结点指针edge[tot].next = head[u];head[u] = tot++;//tot为任一个未被使用的空节点指针。
}
int BFS(int num) {int sum = 0;int flag[MaxSize] = {0};//标记已满足要求的点flag[num]=1;queue<int> q;for (int i = head[num]; i != -1; i = edge[i].next) {q.push(edge[i].to);flag[edge[i].to] = 1;sum++;}for (int i = 2; i <= L; i++) {int Q_size = q.size();//存每一层的大小while (Q_size--) {int t = q.front();//取栈顶q.pop();for (int j = head[t]; j != -1; j = edge[j].next) {if (flag[edge[j].to] != 1) {//若未取过q.push(edge[j].to);flag[edge[j].to] = 1;sum++;}}}}return sum;
}
int main() {cin >> N >> L;memset(head, -1, sizeof head);//输入数据int x, y;for (int i = 1; i <= N; i++) {cin >> x;for (int j = 0; j < x; j++) {cin >> y;add_edge(y, i);}}//查看图的建立是否正确
//	for (int i = 1; i <= MaxSize; i++) { //从1开始
//		for (int j = head[i]; j != -1; j = edge[j].next) {
//			printf("结点%d->结点%d\\n", i, edge[j].to);
//		}
//	}//咨询K个人的转发量cin >> K;int num;while (K--) {cin >> num;cout << BFS(num)<<endl;}
}

二、DFS试炼之不同路径数

DFS试炼之不同路径数

描述

给定一个n×m的二维矩阵,其中的每个元素都是一个[1,9]之间的正整数。

从矩阵中的任意位置出发,每次可以沿上下左右四个方向前进一步,走过的位置可以重复走。

走了k次后,经过的元素会构成一个(k+1)位数。

请求出一共可以走出多少个不同的(k+1)位数。

输入

第一行包含三个整数n,m,k。

接下来n行,每行包含m个空格隔开的整数,表示给定矩阵。

数据范围

1≤n,m≤5,

0≤k≤5,

m×n>1

输出

输出一个整数,表示可以走出的不同(k+1)位数的个数。

输入样例 1

3 3 2
1 1 1
1 1 1
2 1 1

输出样例 1

5

解题思路:

  • 根据题意,可以用DFS遍历得到,从[x,y]出发走k步的每一种情况
  • 将每种情况存入set集合即可自动去重
  • 注意:每一步不能同时走x,y

代码实现:

#include<iostream>
#include<set>
using namespace std;
set<int> s;
int n, m, k;
int Arr[10][10];int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0,-1};//一次走一步,走x不走y
void dfs(int x, int y, int q, int value) { //第q步的横坐标x,纵坐标y,当前值valueif (q == k) {//若为第k步,将当前值加入sets.insert(value);return ;}for (int i = 0; i < 4; i++) {int xx = x + dx[i];int yy = y + dy[i];if (xx < n && yy < m && xx >= 0 && yy >= 0)dfs(xx, yy, q + 1, value * 10 + Arr[xx][yy]);}
}
int main() {cin >> n >> m >> k;for (int i = 0; i < n; i++)for (int j = 0; j < m; j++)cin >> Arr[i][j];//选择起点for (int i = 0; i < n; i++)for (int j = 0; j < m; j++)dfs(i, j, 0, Arr[i][j]);cout << s.size();return 0;
}

三、并查集试炼之合并集合

并查集试炼之合并集合

描述

一共有n个数,编号是1~n,最开始每个数各自在一个集合中。

现在要进行m个操作,操作共有两种:

  1. “M a b”,将编号为a和b的两个数所在的集合合并,如果两个数已经在同一个集合中,则忽略这个操作;
  2. “Q a b”,询问编号为a和b的两个数是否在同一个集合中;

数据范围:1≤n,m≤10^5

输入

第一行输入整数n和m。

接下来m行,每行包含一个操作指令,指令为“M a b”或“Q a b”中的一种。

输出

对于每个询问指令”Q a b”,都要输出一个结果,如果a和b在同一集合内,则输出“Yes”,否则输出“No”。

每个结果占一行。

输入样例 1

4 5
M 1 2
M 3 4
Q 1 2
Q 1 3
Q 3 4

输出样例 1

Yes
No
Yes

并查集的介绍

定义

并查集是一种树型的数据结构,用于处理一些不相交集合的合并及查询问题(即所谓的并、查)。

基本操作

  • pre[]数组:存储每个结点的父结点

  • 查找(Find):查询两个元素是否在同一个集合中(两个元素的祖宗结点是否相同)

  • 合并(Union):把两个不相交的集合合并为一个集合(将A的父结点设为B)


解题思路:

由题可知,此题为非常典型的并查集问题。

代码实现:

#include<iostream>
using namespace std;
#define MaxSize 100050
int pre[MaxSize];
int Find(int x) {//找祖宗结点if (x == pre[x])//若为本身,即为祖宗结点return x;elsereturn Find(pre[x]);//找父结点的父结点
}
void Union(int x, int y) {//合并x,y,即为祖宗结点统一pre[Find(x)] = Find(y);//x的祖宗结点的父节点为y的祖宗结点
}
int main() {int n, m;cin >> n >> m;//初始化,每个元素都是自己的父元素for (int i = 1; i <= n; i++)pre[i] = i;while (m--) {char order;int a, b;cin>>order>>a>>b;if (order=='M')Union(a, b);else {if (Find(a) != Find(b))cout << "No" << endl;elsecout << "Yes" << endl;}}
}

四、堆排序

堆排序

描述

输入一个长度为n的整数数列,从小到大输出前m小的数。

数据范围:

1≤m≤n≤10^5,

1≤数列中元素≤10^9

输入

第一行包含整数n和m。

第二行包含n个整数,表示整数数列。

输出

共一行,包含m个整数,表示整数数列中前m小的数。

输入样例 1

5 3
4 5 1 3 2

输出样例 1

1 2 3

提示

原题链接


堆排序的介绍

堆排序的基本概念介绍

代码实现:

#include<iostream>
using namespace std;
int N,M;
int arr[100050];
void HeadAdjust(int arr[], int k, int len) {arr[0] = arr[k];for (int i = 2 * k; i <= len; i *= 2) {if (arr[i] < arr[i + 1] && i + 1 <= len) { //将i指向较大子孩子i++;}if (arr[0] > arr[i])break;else {arr[k] = arr[i];k = i;}}arr[k] = arr[0];
}
void buildMaxHead(int arr[], int length) {for (int i = length / 2; i >= 1; i--) { HeadAdjust(arr, i, length);}
}int main() {cin >> N>>M;for (int i = 1; i <= N; i++) {cin >> arr[i];}buildMaxHead(arr, N);for (int i = N; i >= 1;) {int t = arr[i];arr[i] = arr[1];arr[1] = t;HeadAdjust(arr,1,--i);}for (int i = 1; i <= M; i++) {if (i != 1)cout << " ";cout << arr[i];}
}

五、厦大GPA(分组背包)

厦大GPA

描述

厦门大学的GPA (绩点)计算规则一直是同学们非常关心的问题。每门考试成绩为百分制,则分数与绩点对应关系如下:

90~100 4.0

85~89 3.7

81~84 3.3

78~80 3.0

75~77 2.7

72~74 2.3

68~71 2.0

64~67 1.7

60~63 1.0

0~59 0.0

某位同学一共参加了4门考试,给定四门考试的总分,请问在最优情况下,4门考试绩点的和最高是多少?

输入

输入4门考试的总分n,0≤n≤400。

输出

输出最优情况下,4门考试绩点之和的最高值。结果保留一位小数。

输入样例 1

400
359
59

输出样例 1

16.0
15.7
0.0

解题思路:

由题可知,给定了总和(总分),和每个元素的价值(绩点)和代价(分数)。求在不超过总和(总分)的情况下,最多能有多少价值(绩点)。可知,为背包问题。但注意不是01背包或完全背包问题。01背包和完全背包中,给定的是可选物品的数目,而没有给定背包格子数。

此题要求:

  • 只能选4个物品
  • 体积总和不大于总体积
  • 可重复选择同一物品

可看出,为分组背包问题

而对于题目中给予的区间,可以只取区间的下限来达到最高性价比。(90~100为4.0。即为体积为90的物品,价值为4.0)

分组背包介绍

给你一个容量为V的背包,和N组物品,每一组至多选择一个物品(也可以不选),每个物品都有自己的体积和价值

动态规划转移方程为:
f [ i ] [ j ] = m a x { f [ i ] [ j ] , f [ i − 1 ] [ j − v [ i ] [ 1 ] ] + w [ i ] [ 1 ] , f [ i − 1 ] [ j − v [ i ] [ 2 ] ] + w [ i ] [ 2 ] , . . . , f [ i − 1 ] [ j − v [ i ] [ k ] ] + w [ i ] [ k ] } f[i][j]=max\\{f[i][j]\\\\,f[i-1][j-v[i][1]]+w[i][1]\\\\,f[i-1][j-v[i][2]]+w[i][2]\\\\,...\\\\,f[i-1][j-v[i][k]]+w[i][k]\\} f[i][j]=max{f[i][j],f[i1][jv[i][1]]+w[i][1],f[i1][jv[i][2]]+w[i][2],...,f[i1][jv[i][k]]+w[i][k]}
其中f[i][j]与自身的比较,即可限定每一组最多选一个物品。

核心代码:

for(int i=1;i<=N;i++)//N组物品for(int j=0;j<=V;j++)//遍历体积0~Vfor(int k=1;k<=s[i];k++)//s[i]表示第i组物品的个数if(j>=v[i][k])//剩余的背包容量j大于第i组的第k个物品的体积 {f[i][j] = max(f[i][j],f[i-1][j-v[i][k]]+w[i][k]);}

代码实现:

#include<iostream>
#include<algorithm>
#include<string.h>;
int V[11] = {0, 90, 85, 81, 78, 75, 72, 68, 64, 60, 0}; //分数
double A[11] = {0, 4.0, 3.7, 3.3, 3.0, 2.7, 2.3, 2.0, 1.7, 1.0, 0.0}; //绩点
using namespace std;
int main() {int n, v;n = 4;while (cin >> v) {double dp[n + 5][v + 5];memset(dp, 0, sizeof dp);for (int i = 1; i <= n; i++) {//第i组背包for (int j = v; j >= 0; j--) { //现在体积for (int k = 1; k <= 10; k++) {//选择前j个物品if (j >= V[k]) {dp[i][j] = max(dp[i][j], dp[i - 1][j - V[k]] + A[k]);} }}}printf("%.1lf\\n", dp[n][v]);}return 0;
}

六、消防安全指挥问题(最短路Floyd)

消防安全指挥问题

描述

假设你是翔安校区的消防安全指挥官,负责管理翔安校区的所有学生公寓的消防安全,一共有N个消防队,这N个消防队驻扎在不同的学生公寓,你们一起保卫翔安校区M所学生公寓的消防安全,其中学生公寓的的编号从1到M。

突然有一天,消防安全系统发出警报,显示第K所学生公寓可能有火情,你要通知这N个消防队让他们派遣分队,从驻扎的公寓沿着最近的路线紧急前往第K所学生公寓。

现在已知任意2所学生公寓之间的通行时间,请你计算出第一个消防分队到达第K所学生公寓的通行时间。

输入

第一行:输入一个整数T,表示测试数据的组数。(T<20)

每组测试数据的第一行:四个整数N,M,P,Q(1<=N<=100,N<=M<=1000,M-1<=P<=100000)其中N表示消防队数,M表示学生公寓数,P表示学生公寓之间的路的条数,Q表示发生火情的学生公寓编号。

随后的一行是N个整数,表示消防部队驻扎的学生公寓编号。

再之后的P行,每行有三个正整数,a,b,t(1<=a,b<=M,1<=t<=100),表示第a,b所学生公寓通行所花费的时间。

输入的数据保证发生火情的学生公寓是可达的。

另外,两个学生公寓之间可能不只一条路。

输出

对于每组测试数据,输出第一支消防队到达发生火情的学生公寓的时间。

每组输出占一行。

输入样例 1

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

输出样例 1

4

解题思路:

  • 处理两个学生公寓间多条路

    选取花费时间最少的一条

  • 由题可知,可转化为多源最短路问题。求多源中,路径最短(花费时间最少)的一条

  • 多源最短路有两种解决方案:

    • 做N次dijlstra单源最短路
    • 用floyd

时间复杂度一样,floyd更简单,我选择用floyd

Floyd算法基本步骤:

  • 初始化:

    • 将对角线(i,i)全部置0(自身到自身为0)
    • 不可直接到达的设为无穷
    • 有边相连的两点的直接赋予边的权值
  • 加点

    • 在原有两点A,B间加入第三点C,如果加入后距离变小即更新
    • 将数组中所有的点都当做中间点遍历一次,即可得到最终结果

代码实现

#include<iostream>
#include<string.h>
#include<algorithm>
#define MaxSize 1050
using namespace std;
int fireman[200];
int N, M, P, Q;
int e[MaxSize][MaxSize];
bool infireman(int x) {//有消防队员驻扎for (int i = 0; i <N; i++) { //编号从1~Nif (x == fireman[i])return true;}return false;
}
int main() {int T;cin >> T;while (T--) {cin >> N >> M >> P >> Q;//N-消防队数,M-学生公寓数,P-学生公寓之间路数,Q-发生火灾的公寓编号memset(fireman, -1, sizeof fireman);for (int i = 0; i <N; i++) { //编号从1~Ncin >> fireman[i];}memset(e, 0x3f3f3f, sizeof e);for (int i = 1; i <= M; i++) { //对角线距离为0e[i][i] = 0;}int a, b, t;while (P--) {cin >> a >> b >> t;e[a][b]=e[b][a]=min(e[a][b],t);}//floyd算法int Min = 0x3f3f3f;for (int k = 1; k <= M; k++) { //加入第k个结点for (int i = 1; i <= M; i++) {for (int j = 1; j <= M; j++) {e[i][j]=min(e[i][j],e[i][k] + e[k][j]);if (j == Q && infireman(i)) {Min =min(Min,e[i][j]);}}}}cout << Min<<endl;}return 0;
}

七、铺设光纤问题(最小生成树Prim)

铺设光纤问题

描述

开学季来临,联通通信公司中标翔安校区的宽带业务,需要在所有学生公寓之间铺设光纤接入宽带网,面对同行电信和移动的竞争压力,联通宽带业务组长需要压缩铺设光纤的成本。

现在给出各个学生公寓之间的光纤铺设成本的一览表,现在请你替他们找到能够连接所有学生公寓并使铺设的光纤最短的方案。

输入

第一行:学生公寓的个数,N(3≤N≤100)。

接下来的N行:一个N×N的矩阵,表示每所学生公寓之间的距离dij。用空格分隔。(dij<=100000)

输出

能连接所有学生公寓所铺设的光纤的最小长度。

输入样例 1

4
0 4 9 21
4 0 8 17
9 8 0 16
21 17 16 0

输出样例 1

28

解题思路:

由题可知,为典型的最小生成树问题,用Prim或者kruskal都可以

我用的是Prim

代码实现:

//prim最小生成树
#include<iostream>
#include<string.h>
#define MaxSize 200
using namespace std;
int Graph[MaxSize][MaxSize];
int PrimSet[MaxSize];//存储已加入的结点
int N;
int S = 0;//已加入的结点个数void CreateGraph() {//创建无向带权图for(int i=1;i<=N;i++){for(int j=1;j<=N;j++){cin>>Graph[i][j];}}
}void Create_iCost(int i, int &min, int &v) { //找到结点i的目前最近结点min = 0x3f3f3f;v = -1;for (int j = 1; j <= N; j++) {if (j == i || Graph[i][j] == -1 || PrimSet[j]==1) //若j已加入continue;if (Graph[i][j] < min) {min = Graph[i][j];v = j;}}
}
int Prim(int v) {PrimSet[v] = 1; //1~NS++;int SumCost = 0;while (S != N) {//找出PrimSet中与其他新点距离最短的那一个int min = 0x3f3f3f;int temp, p, q;int flag = 0;for (int i = 1; i <= N; i++) {if (PrimSet[i] != 1)continue;Create_iCost(i, p, q);if (p < min) {//若i没有除PrimSet外的连接点,p为0x3f3f3fmin = p;temp = q;flag = 1;}}if (flag == 0) { //若不连通return -1;}PrimSet[temp] = 1;S++;SumCost += min;}return SumCost;
}int main() {scanf("%d", &N);memset(Graph, -1, sizeof(int)*MaxSize * MaxSize);CreateGraph();int SumCost = Prim(1); //从结点1出发建立最小生成树,Prim算法printf("%d", SumCost);
}

八、CCF-A会报告(多重背包问题)

CCF A会报告

描述

作为计算机相关专业的本科生,你对CCF A类论文有所耳闻。CCF协会推荐的A类会议期刊中的论文代表了在计算机领域最高水平的论文。每年你所在的实验室都会举办若干场学术报告并表彰在A类会议上发表论文的同学。作为学术报告的负责人,你需要用有限的经费来奖励对应同学,让他们获得对于他们价值最大的奖品,比如移动硬盘、显示器和GPU 4090等等。

然而物品是有限的,第i个物品的价格为w[i]、参考的价值为v[i],能买到的最大物品数量为s[i],你需要以你的经验来购买获得最大的价值。

输入

第一行2个数n(n<=500),m(m<=6000),其中n代表物品的种数,m表示你的经费。

接下来n行,每行3个数,w[i] (w[i]<=100)、v[i] (v[i]<=1000)、s[i] (s[i]<=10)。

输出

一个整数,即最大获得的价值

输入样例 1

5 1000
80 20 4
40 50 9
30 50 7
40 30 6
20 20 1

输出样例 1

1040

提示

其实供应商那里还有更多的存货,s[i]<=1000的时候你还能计算吗?


解题思路:

由题可知,为典型的多重背包问题。

多重背包介绍

有 N种物品和一个容量是 V 的背包,每种物品都有s[i]件 (01背包 和完全背包 可以看作多重背包的特殊情况)

动态规划状态转移方程为:
d p [ i ] [ j ] = m a x { d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − w ] + v , d p [ i − 1 ] [ j − 2 w ] + 2 v , . . . , d p [ i − 1 ] [ j − k w ] + k v } dp[i][j]=max\\{dp[i-1][j],dp[i-1][j-w]+v,dp[i-1][j-2w]+2v,...,dp[i-1][j-kw]+kv\\} dp[i][j]=max{dp[i1][j],dp[i1][jw]+v,dp[i1][j2w]+2v,...,dp[i1][jkw]+kv}
其中k为第i个物品的物品数,即为题中的 s [ i ] s[i] s[i]

核心代码为:

if (j < w[i] || s[i] == 0)//若剩余体积不足,或物品数为0dp[i][j] = dp[i - 1][j];
else {dp[i][j] = max(dp[i - 1][j], dp[i - 1][j -  w[i]] + v[i]);for (int k = 2; k <= s[i]; k++) {if (j >= k * w[i])dp[i][j] = max(dp[i][j], dp[i - 1][j - k * w[i]] + k * v[i]);}

代码实现:

//多重背包问题
#include<iostream>
#include<string.h>
#include<algorithm>
using namespace std;
#define MaxSize 600
int w[MaxSize], v[MaxSize], s[MaxSize]; //价格、价值、最大物品数
int dp[600][6100];
int main() {int n, m;cin >> n >> m;for (int i = 1; i <= n; i++)cin >> w[i] >> v[i] >> s[i];memset(dp, 0, sizeof dp);for (int i = 1; i <= n; i++) {for (int j = 1; j <= m; j++) {if (j < w[i] || s[i] == 0)//若剩余体积不足,或物品数为0dp[i][j] = dp[i - 1][j];else {dp[i][j] = max(dp[i - 1][j], dp[i - 1][j -  w[i]] + v[i]);for (int k = 2; k <= s[i]; k++) {if (j >= k * w[i])dp[i][j] = max(dp[i][j], dp[i - 1][j - k * w[i]] + k * v[i]);}}}}cout << dp[n][m];
}