第四次解题报告
第一题 连通分量
-
题目描述
无向图 G 有 n 个顶点和 m 条边。求 G 的连通分量的数目。
输入格式:
第1行,1个整数n,表示森林的结点个数, 1≤n≤100000.
第2行,n个字符,用空格分隔,表示森林F的先根序列。字符为大小写字母及数字。
第3行,n个整数,用空格分隔,表示森林F的先根序列中每个结点对应的度。
输出格式:
1行,n个字符,用空格分隔,表示森林F的层次遍历序列。
输入样例:
在这里给出一组输入。例如:
6 5
1 3
1 2
2 3
4 5
5 6
输出样例:
在这里给出相应的输出。例如:
2
-
题目分析
本题要求求出给定无向图中连通分量的数目。需要注意,无向图的连通分量与有向图的强连通分量有很大区别。
求解无向图的连通分量数较为简单,只需使用DFS或BFS进行搜索,每次搜索都能将相连的顶点遍历,故搜索的次数即为连通分量的个数。
每个顶点都会被处理一次,总的时间复杂度为O(n)。
-
算法实现
#include<bits/stdc++.h> using namespace std; vector<int> graph[50001]; int visited[50001]={0}; void DFS(int index) { visited[index]=1; for(int i=1;i<graph[index].size();i++) { if(visited[graph[index][i]]!=1) DFS(graph[index][i]); } } int main() { int n,m; scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) { graph[i].push_back(-1); } for(int i=1;i<=m;i++) { int cur; scanf("%d",&cur); int tmp; scanf("%d",&tmp); graph[cur].push_back(tmp); graph[tmp].push_back(cur); } int count_=0; for(int i=1;i<=n;i++) { if(visited[i]!=1) { DFS(i); count_++; } } printf("%d",count_); return 0; }
-
核心数据结构
使用了vector来以邻接表方式存储图。图中每个顶点是否已经过用一个int数组标记。
第二题 整数拆分
-
题目描述
整数拆分是一个古老又有趣的问题。请给出将正整数 n 拆分成 k 个正整数的所有不重复方案。
例如,将 5 拆分成 2 个正整数的不重复方案,有如下2组:(1,4)和(2,3)。注意(1,4) 和(4,1)被视为同一方案。每种方案按递增序输出,所有方案按方案递增序输出。
输入格式:
1行,2个整数n和k,用空格分隔, 1≤k≤n≤50.
输出格式:
若干行,每行一个拆分方案,方案中的数用空格分隔。
最后一行,给出不同拆分方案的总数。
输入样例:
在这里给出一组输入。例如:
5 2
输出样例:
在这里给出相应的输出。例如:
1 4
2 3
2
-
题目分析
本题是很经典的题目。在程序设计基础课程中就有涉及。当时,课本给出的方法是试探法,即满足条件时延长答案序列或输出,不满足时回溯。
不难发现,这种试探法的思想就是深度优先搜索的思想,当前深度不满足条件时就退回上一深度进行搜索。
此题用这样的搜索回溯的方法即可解决。将当前数拆为两个数,再如此递归调用,同时通过判断大小和巧妙设置初值保证序列递增。
本算法的复杂度尚未得出。
-
算法实现
#include<bits/stdc++.h> using namespace std; int k; int ans[51]; int count_=0; void DFS(int left,int index)//left为待分解的值,index为当前答案的位置 { if(index==k)//递归出口 { if(left>=ans[index-1])//保证答案序列递增 { count_++; for(int i=1;i<k;i++) { printf("%d ",ans[i]); } printf("%d\n",left); return; } else return; } for(int cur=ans[index-1];cur<=left;cur++)//每次都从答案上一个值的相等值开始,保证答案序列递增 { ans[index]=cur; //出现新值直接覆盖实现回溯 DFS(left-cur,index+1); } } int main() { int n; scanf("%d%d",&n,&k); ans[0]=1; //DFS中每次当前答案都需从上一答案得出,故需有ans[0]=1 DFS(n,1); printf("%d",count_); return 0; }
-
核心数据结构
全局数组ans用于保存当前分解结果。
第三题 数字变换
-
题目描述
利用变换规则,一个数可以变换成另一个数。变换规则如下:(1)x 变为x+1;(2)x 变为2x;(3)x 变为 x-1。给定两个数x 和 y,至少经过几步变换能让 x 变换成 y.
输入格式:
1行,2个整数x和y,用空格分隔, 1≤x,y≤100000.
输出格式:
第1行,1个整数s,表示变换的最小步数。
第2行,s个数,用空格分隔,表示最少变换时每步变换的结果。规则使用优先级顺序: (1),(2),(3)。
输入样例:
在这里给出一组输入。例如:
2 14
输出样例:
在这里给出相应的输出。例如:
4
3 6 7 14
-
题目分析
由题知,每一个值在变换过程中都能产生三个新的值,所以变换对应了一颗三叉树。
求解最短的变换过程,从而就可以转化为在三叉树中通过层次遍历(BFS)寻找第一个值与答案相同的结点,并输出其所在的路径。
所以可以用一个数组来保存所有计算过程中得到的值,再用另一个数组保存每个值的前驱值,加以BFS的辅助队列,即可解决问题。
.由于树的高度根据x、y的值发生变化,本算法的时间复杂度较难估计。
算法实现
#include<bits/stdc++.h>
using namespace std;
vector<int> result; //保存变换过程中生成的所有结果
vector<int> former; //保存每个结果的前驱值的下标
queue<int> assi; //层次遍历/BFS辅助队列
stack<int> inverse; //逆转最少变换序列所用栈
int main()
{
int x,y;
scanf("%d%d",&x,&y);
result.push_back(x); //初始化
former.push_back(-1);
assi.push(0);
if(x==y)
assi.pop();
int ans=0;
int count_=1;
while(!assi.empty())
{
int tmp;
tmp=assi.front();
assi.pop();
if(result[tmp]+1!=y)//变换1
{
result.push_back(result[tmp]+1);
former.push_back(tmp);
assi.push(count_);
count_++;
}
else
{
ans=tmp;
break;
}
if(result[tmp]*2!=y)//变换2
{
result.push_back(result[tmp]*2);
former.push_back(tmp);
assi.push(count_);
count_++;
}
else
{
ans=tmp;
break;
}
if(result[tmp]-1!=y)//变换3
{
result.push_back(result[tmp]-1);
former.push_back(tmp);
assi.push(count_);
count_++;
}
else
{
ans=tmp;
break;
}
}
int steps=0;
while(ans!=0) //逆序存入变换中间值
{
steps++;
inverse.push(ans);
ans=former[ans];
}
if(x!=y)
steps++;
printf("%d\n",steps);
while(!inverse.empty()) //输出答案
{
int tmp=inverse.top();
inverse.pop();
printf("%d ",result[tmp]);
}
printf("%d",y);
return 0;
}
-
核心数据结构
队列assi,用于完成层次遍历(BFS)。
第四题 旅行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算法,时间复杂度为O(n)。
算法实现
#include<bits/stdc++.h>
using namespace std;
#define MAX 1e5
vector<int> graph[10001];
vector<int> weight[10001];
int visited[10001]={0};
int dist[10001];
int path[10001];
int main()
{
int n,m,s;
scanf("%d%d%d",&n,&m,&s);
for(int i=1;i<=n;i++)
{
graph[i].push_back(0);
weight[i].push_back(0);
}
for(int i=1;i<=m;i++)
{
int cur;
scanf("%d",&cur);
int tmp;
scanf("%d",&tmp);
graph[cur].push_back(tmp);
graph[tmp].push_back(cur);
int cost;
scanf("%d",&cost);
weight[cur].push_back(cost);
weight[tmp].push_back(cost);
}
for(int i=1;i<=n;i++)//初始化
{
dist[i]=MAX;
path[i]=0;
}
dist[s]=0; //s为选择的源点
for(int j=1;j<n;j++)//Dijkstra算法
{
int cur;
int mindist=MAX;
for(int i=1;i<=n;i++)
{
if(dist[i]<mindist&&visited[i]==0)
{
mindist=dist[i];
cur=i;
}
}
visited[cur]=1;
for(int i=1;i<graph[cur].size();i++)
{
if(dist[cur]+weight[cur][i]==dist[graph[cur][i]])//=成立时,判断当前路径是否经过城市更多
{
if(path[graph[cur][i]]<path[cur]+1)
path[graph[cur][i]]=path[cur]+1;
}
else if(dist[cur]+weight[cur][i]<dist[graph[cur][i]])
{
dist[graph[cur][i]]=dist[cur]+weight[cur][i];
path[graph[cur][i]]=path[cur]+1;
}
}
}
for(int i=1;i<=n;i++)
{
printf("%d",dist[i]);
if(i!=n)
printf(" ");
}
printf("\n");
for(int i=1;i<=n;i++)
{
printf("%d",path[i]);
if(i!=n)
printf(" ");
}
return 0;
}
-
核心数据结构
使用了标准模板库STL的vecotr容器来实现无向图以邻接表方式实现的存储。