7-1 旅游规划
有了一张自驾旅游路线图,你会知道城市间的高速公路长度、以及该公路要收取的过路费。现在需要你写一个程序,帮助前来咨询的游客找一条出发地和目的地之间的最短路径。如果有若干条路径都是最短的,那么需要输出最便宜的一条路径。
输入格式:
输入说明:输入数据的第1行给出4个正整数N、M、S、D,其中N(2≤N≤500)是城市的个数,顺便假设城市的编号为0~(N−1);M是高速公路的条数;S是出发地的城市编号;D是目的地的城市编号。随后的M行中,每行给出一条高速公路的信息,分别是:城市1、城市2、高速公路长度、收费额,中间用空格分开,数字均为整数且不超过500。输入保证解的存在。
输出格式:
在一行里输出路径的长度和收费总额,数字间以空格分隔,输出结尾不能有多余空格。
输入样例:
4 5 0 3
0 1 1 20
1 3 2 30
0 3 4 10
0 2 2 20
2 3 1 20
输出样例:
3 40
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int INF=501;
int main(){
int N,M,S,D;
cin>>N>>M>>S>>D;
int r[INF][INF],c[INF][INF];
//init
int i,j;
for(i=0;i<INF;i++)
for(j=0;j<INF;j++){
r[i][j]=501;
c[i][j]=501;
}
bool flag[INF]={false};
flag[S]=true;
vector<int> visit;
visit.push_back(S);
while(M--){
cin>>i>>j;
cin>>r[i][j]>>c[i][j];
r[j][i]=r[i][j];
c[j][i]=c[i][j];
}
int dist[N],cost[N];
for(i=0;i<N;i++){
dist[i]=r[S][i];
cost[i]=c[S][i];
}
//start
while(visit.size()!=N){
int v=501,mindist=501;
for(i=0;i<N;i++){
if(!flag[i]&&dist[i]<mindist){
v=i;
mindist=dist[i];
}
}
visit.push_back(v);
flag[v]=true;
for(int u=0;u<N;u++){
if(r[v][u]!=501){
if(dist[v]==dist[u]+r[u][v]){
cost[v]=min(cost[v],cost[u]+c[u][v]);
continue;
}
int temp=dist[v];
dist[v]=min(dist[v],dist[u]+r[u][v]);
if(dist[v]==temp)
cost[v]=cost[v];
else
cost[v]=cost[u]+c[u][v];
}
}
}
cout<<dist[D]<<" "<<cost[D]<<endl;
//for(i=0;i<N;i++)
//cout<<dist[i]<<" ";
//cout<<endl;
//for(i=0;i<N;i++)
//cout<<cost[i]<<" ";
return 0;
}
7-2 关键活动
假定一个工程项目由一组子任务构成,子任务之间有的可以并行执行,有的必须在完成了其它一些子任务后才能执行。“任务调度”包括一组子任务、以及每个子任务可以执行所依赖的子任务集。
比如完成一个专业的所有课程学习和毕业设计可以看成一个本科生要完成的一项工程,各门课程可以看成是子任务。有些课程可以同时开设,比如英语和C程序设计,它们没有必须先修哪门的约束;有些课程则不可以同时开设,因为它们有先后的依赖关系,比如C程序设计和数据结构两门课,必须先学习前者。
但是需要注意的是,对一组子任务,并不是任意的任务调度都是一个可行的方案。比如方案中存在“子任务A依赖于子任务B,子任务B依赖于子任务C,子任务C又依赖于子任务A”,那么这三个任务哪个都不能先执行,这就是一个不可行的方案。
任务调度问题中,如果还给出了完成每个子任务需要的时间,则我们可以算出完成整个工程需要的最短时间。在这些子任务中,有些任务即使推迟几天完成,也不会影响全局的工期;但是有些任务必须准时完成,否则整个项目的工期就要因此延误,这种任务就叫“关键活动”。
请编写程序判定一个给定的工程项目的任务调度是否可行;如果该调度方案可行,则计算完成整个工程项目需要的最短时间,并输出所有的关键活动。
输入格式:
输入第1行给出两个正整数N(≤100)和M,其中N是任务交接点(即衔接相互依赖的两个子任务的节点,例如:若任务2要在任务1完成后才开始,则两任务之间必有一个交接点)的数量。交接点按1~N编号,M是子任务的数量,依次编号为1~M。随后M行,每行给出了3个正整数,分别是该任务开始和完成涉及的交接点编号以及该任务所需的时间,整数间用空格分隔。
输出格式:
如果任务调度不可行,则输出0;否则第1行输出完成整个工程项目需要的时间,第2行开始输出所有关键活动,每个关键活动占一行,按格式“V->W”输出,其中V和W为该任务开始和完成涉及的交接点编号。关键活动输出的顺序规则是:任务开始的交接点编号小者优先,起点编号相同时,与输入时任务的顺序相反。
输入样例:
7 8
1 2 4
1 3 3
2 4 5
3 4 3
4 5 1
4 6 6
5 7 5
6 7 2
输出样例:
17
1->2
2->4
4->6
6->7
代码如下:
#include<iostream>
#include<vector>
#include<queue>
using namespace std;
const int N = 1010;
struct Edge{
int to,w;
};
int n,m;
int in[N],out[N],early[N],last[N];
vector<Edge> eg[N],lg[N];
vector<int> sorted;//存储拓扑序列
priority_queue<int,vector<int>,greater<int>> que;
bool TopSorted(){ //拓扑排序
int cnt = 0;
for(int i = 1; i <= n; i++){
if(in[i] == 0){
que.push(i);
sorted.push_back(i);
cnt++;
}
}
while(que.size()){
int tmp = que.top();
que.pop();
for(Edge x:eg[tmp]){
in[x.to]--;
if(in[x.to] == 0){
que.push(x.to);
sorted.push_back(x.to);
cnt++;
}
}
}
if(cnt == n) return true;
else return false;
}
int main(){
cin>>n>>m;
for(int i = 0; i < m; i++){
int u,v,w;
cin>>u>>v>>w;
eg[u].push_back({v,w});
lg[v].push_back({u,w});
in[v]++;
out[u]++;
}
if(TopSorted()){
//计算earlyTime(最早开始时间)
int maxx = 0;
for(int p:sorted){
for(Edge tmp:eg[p]){//找到p指向的结点进行更新
if(tmp.w + early[p] > early[tmp.to]){
early[tmp.to] = tmp.w + early[p];
maxx = max(maxx,early[tmp.to]);
}
}
}
cout<<maxx<<endl;
//计算lastTime(最晚开始时间)
int minn = 0x3f3f3f3f;
for(int p:sorted){
last[p] = 0x3f3f3f3f;
if(out[p] == 0){
last[p] = maxx;
}
}
//拓扑序列反向遍历
for(auto i = sorted.rbegin(); i != sorted.rend(); i++){
int p = *i;
for(Edge tmp:lg[p]){//找到指向p的结点进行更新
if(last[p] - tmp.w < last[tmp.to]){
last[tmp.to] = last[p] - tmp.w;
}
}
}
for(int i = 1; i <= n; i++){//题目要求交接点小到大
for(auto it = eg[i].rbegin(); it != eg[i].rend(); it++){//与输入的顺序相反
Edge p = *it;
if(last[p.to] - p.w == early[i])//关键活动
printf("%d->%d\n",i,p.to);
}
}
}else {
cout<<0;
}
return 0;
}
7-3 最短工期
一个项目由若干个任务组成,任务之间有先后依赖顺序。项目经理需要设置一系列里程碑,在每个里程碑节点处检查任务的完成情况,并启动后续的任务。现给定一个项目中各个任务之间的关系,请你计算出这个项目的最早完工时间。
输入格式:
首先第一行给出两个正整数:项目里程碑的数量 N(≤100)和任务总数 M。这里的里程碑从 0 到 N−1 编号。随后 M 行,每行给出一项任务的描述,格式为“任务起始里程碑 任务结束里程碑 工作时长”,三个数字均为非负整数,以空格分隔。
输出格式:
如果整个项目的安排是合理可行的,在一行中输出最早完工时间;否则输出"Impossible"。
输入样例 1:
9 12
0 1 6
0 2 4
0 3 5
1 4 1
2 4 1
3 5 2
5 4 0
4 6 9
4 7 7
5 7 4
6 8 2
7 8 4
输出样例 1:
18
输入样例 2:
4 5
0 1 1
0 2 2
2 1 3
1 3 4
3 2 5
输出样例 2:
Impossible
代码如下:
#include<bits/stdc++.h>
using namespace std;
int dis[105][105],ru[105],f[105];
int n,ans,cnt=0;
void topological_sort(){
while(1){
int flag=0;
for(int i=0;i<n;i++){
if(!ru[i]){
ru[i]--;
cnt++;
flag=1;
for(int j=0; j<n; j++){
if(dis[i][j]!=-1){
ru[j]--;
f[j]=max(f[j],f[i]+dis[i][j]);
ans=max(ans,f[j]);
}
}
}
}
if(!flag) break;
}
if(cnt==n) cout<<ans<<endl;
else cout<<"Impossible"<<endl;
}
int main(){
int m, p, x, y;
cin>>n>>m;
for(int i=0; i<n; i++)
for(int j=0; j<n; j++)
dis[i][j]=-1;
while(m--){
cin>>x>>y>>p;
dis[x][y]=p;
ru[y]++;
}
topological_sort();
return 0;
}
7-4 最小生成树的唯一性
给定一个带权无向图,如果是连通图,则至少存在一棵最小生成树,有时最小生成树并不唯一。本题就要求你计算最小生成树的总权重,并且判断其是否唯一。
输入格式:
首先第一行给出两个整数:无向图中顶点数 N(≤500)和边数 M。随后 M 行,每行给出一条边的两个端点和权重,格式为“顶点1 顶点2 权重”,其中顶点从 1 到N 编号,权重为正整数。题目保证最小生成树的总权重不会超过 230。
输出格式:
如果存在最小生成树,首先在第一行输出其总权重,第二行输出“Yes”,如果此树唯一,否则输出“No”。如果树不存在,则首先在第一行输出“No MST”,第二行输出图的连通集个数。
输入样例 1:
5 7
1 2 6
5 1 1
2 3 4
3 4 3
4 1 7
2 4 2
4 5 5
输出样例 1:
11
Yes
输入样例 2:
4 5
1 2 1
2 3 1
3 4 2
4 1 2
3 1 3
输出样例 2:
4
No
输入样例 3:
5 5
1 2 1
2 3 1
3 4 2
4 1 2
3 1 3
输出样例 3:
No MST
2
代码如下:
#include<bits/stdc++.h>
using namespace std;
struct road{
int u,v,w;
};
bool cmp(road a,road b){
return a.w<b.w;
}
int find(int* p,int a){
if(p[a]==a)return a;
else return find(p,p[a]);
}
void Union(int* p,int a,int b){
int pa=find(p,a);
int pb=find(p,b);
if(pa!=pb){
p[pa]=pb;
}
}
void work(road* r,int* p,int m,int n){
int flag=0,sumw=0,cnt=0;
for(int i=0;i<m;i++){
int pu=find(p,r[i].u);
int pv=find(p,r[i].v);
if(pv!=pu){
for(int j=i+1;j<m;j++){
if(r[i].w==r[j].w){
int puj=find(p,r[j].u);
int pvj=find(p,r[j].v);
if((pu==puj&&pv==pvj)||(pu==pvj&&pv==puj)){
flag=1;
}
}
else break;
}
Union(p,r[i].u,r[i].v);
sumw+=r[i].w;
cnt++;
}
}
if(cnt==n-1){
cout<<sumw<<endl;
if(flag==0) cout<<"Yes";
else cout<<"No";
}
else{
cout<<"No MST"<<endl;
int ucnt=0;
for(int i=1;i<=n;i++)
if(p[i]==i)
ucnt++;
cout<<ucnt;
}
}
int main(){
int n,m;
cin>>n>>m;
int parent[n+1];
for(int i=1;i<=n;i++){
parent[i]=i;
}
road r[m];
for(int i=0;i<m;i++)
cin>>r[i].u>>r[i].v>>r[i].w;
sort(r,r+m,cmp);
//for(int i=0;i<m;i++)
//cout<<r[i].u<<" "<<r[i].v<<" "<<r[i].w<<endl;
work(r,parent,m,n);
return 0;
}
7-5 最长路径
给定一个正权有向无环图和图中的两个顶点,请编写程序找出这两个顶点间的最长路径,若两点间存在多条最长路径,则输出字典序最小者。假定图中包含n个顶点,编号为0至n-1。
注:字典序,即对象在字典中的顺序。对于两个数字序列,从第一个数字开始比较,当某一个位置的数字不同时,该位置数字较小的序列,字典序较小,例如1 2 3 9比1 2 4 5小,1 2 8 9比1 2 10 3小。
输入格式:
输入第一行为3个正整数n和e,分别为图的顶点数和边数,均不超过50。接下来e行表示每条边的信息,每行为3个整数a、b、c,其中a和b表示该边的端点编号,c表示权值。各边并非按端点编号顺序排列。接下来1行为两个整数s和t,表示两个顶点编号。
输出格式:
输出顶点s到顶点t的字典序最小最长路径,路径中顶点编号用“->”间隔。如s和t不连通,则输出“none”。
输入样例:
4 4
0 1 1
1 2 1
0 3 1
3 2 2
0 2
输出样例:
0->3->2
代码如下:
#include<bits/stdc++.h>
using namespace std;
int n, e;
int x, y;
vector<int> v[1005];
int book[1005];
int maxstep = -1;
int path[1005];
int temp[1005];
int w[1005][1005];
int maxsum = -1;
int flag = -1;
void dfs(int x, int y, int step, int sum) {
if (x == y) {
flag = 0;
if (sum > maxsum) {
maxsum = sum;
maxstep = step;
for (int i = 1;i < step;i++) {
path[i] = temp[i];
}
}
return;
}
int len = v[x].size();
for (int i = 0;i < len;i++) {
if (book[v[x][i]] == 0) {
book[v[x][i]] = 1;
temp[step] = v[x][i];
dfs(v[x][i], y, step + 1, sum + w[x][v[x][i]]);
temp[step] = 0;
book[v[x][i]] = 0;
}
}
}
int main() {
cin >> n >> e;
for (int i = 0;i < e;i++) {
int a, b, c;
cin >> a >> b >> c;
w[a][b] = c;
v[a].push_back(b);
}
for (int i = 0;i < n;i++)
sort(v[i].begin(), v[i].end());
cin >> x >> y;
path[0] = x;
book[x] = 1;
dfs(x, y, 1, 0);
if (flag == -1) {
cout << "none" << endl;;
return 0;
}
for (int i = 0;i < maxstep;i++) {
if (i == maxstep - 1)
printf("%d", path[i]);
else
printf("%d->", path[i]);
}
}
7-6 最短路径-Dijkstra
城市的道路四通八达,我们经常需要查找从某地出发到其他地方的路径,当然我们希望能最快到达。现得到去每个地方需要花费的时间,现请你编写程序,计算从特定地点出发到所有城市之间的最短时间。
输入格式:
输入的第一行给出城市数目N (1≤N≤10)和道路数目M和1(表示有向图)或0(表示无向图);
接下来的M行对应每个城市间来往所需时间,每行给出3个正整数,分别是两个城市的编号(从1编号到N)和来往两城市间所需时间。最后一行给出一个编号,表示从此编号地点出发。
输出格式:
输出从特定地点出发到达所有城市(按编号1-编号N顺序输出)的距离(用编号1->编号**: 表示 ),如果无路,请输出no path。每个城市占一行。
输入样例:
5 5 1
1 2 2
1 4 8
2 3 16
4 3 6
5 3 3
1
输出样例:
1->1:0
1->2:2
1->3:14
1->4:8
1->5:no path
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int INF=501;
struct Edge{
int to;
int weight;
};
vector<int> Dijkstra(vector<vector<Edge>>& graph,int start){
int n=graph.size();
vector<int> dist(n,INF);
dist[start]=0;
priority_queue<pair<int,int>,vector<pair<int,int>>,greater<pair<int,int>>> pq;
pq.push({0,start});
while(!pq.empty()){
int u=pq.top().second;
int d=pq.top().first;
pq.pop();
if(d>dist[u])
continue;
for(const auto& edge:graph[u]){
int v=edge.to;
int weight=edge.weight;
if(dist[u]+weight<dist[v]){
dist[v]=dist[u]+weight;
pq.push({dist[v],v});
}
}
}
return dist;
}
int main(){
int N,M,directed;
cin>>N>>M>>directed;
vector<vector<Edge>> graph(N);
for (int i=0;i<M;i++){
int u,v,weight;
cin>>u>>v>>weight;
graph[u-1].push_back({v-1,weight});
if(directed==0)
graph[v-1].push_back({u-1,weight});
}
int start;
cin>>start;
vector<int> shortestDistances=Dijkstra(graph,start-1);
for(int i = 0; i < N; i++){
if(shortestDistances[i]==INF) cout<<start<<"->"<<i+1<<":no path"<<endl;
else cout<<start<<"->"<<i+1<<":"<<shortestDistances[i]<<endl;
}
return 0;
}
7-7 图深度优先遍历
编写程序对给定的有向图(不一定连通)进行深度优先遍历,图中包含n个顶点,编号为0至n-1。本题限定在深度优先遍历过程中,如果同时出现多个待访问的顶点,则优先选择编号最小的一个进行访问,以顶点0为遍历起点。
输入格式:
输入第一行为两个整数n和e,分别表示图的顶点数和边数,其中n不超过20000,e不超过50。接下来e行表示每条边的信息,每行为两个整数a、b,表示该边的端点编号,但各边并非按端点编号顺序排列。
输出格式:
输出为一行整数,每个整数后一个空格,即该有向图的深度优先遍历结点序列。
输入样例1:
3 3
0 1
1 2
0 2
输出样例1:
0 1 2
输入样例2:
4 4
0 2
0 1
1 2
3 0
输出样例2:
0 1 2 3
代码如下:
#include<bits/stdc++.h>
using namespace std;
int visit[20005],n;
vector<int> road[20005];
void dfs(int cur){
for(int i=0;i<n;i++)
if(visit[i]==0){
cout<<cur<<" ";
visit[cur]=1;
int len=road[cur].size();
for(int i=0;i<len;i++)
if(visit[road[cur][i]]==0)
dfs(road[cur][i]);
}
}
int main(){
int e;
cin>>n>>e;
int a,b;
for(int i=1;i<=e;i++){
cin>>a>>b;
road[a].push_back(b);
}
for(int i=0;i<n;i++)
sort(road[i].begin(),road[i].end());
dfs()
}
7-8 图的先广搜索
输出无向图的给定起点的先广序列。
输入格式:
输入第一行给出三个正整数,分别表示无向图的节点数N(1<N≤10)、边数M(≤50)和探索起始节点编号S(节点从1到N编号)。
随后的M行对应M条边,每行给出一对正整数,分别是该条边直接连通的两个节点的编号。
输出格式:
输出从S开始的无向图的先广搜索序列(用编号表示每个节点),用一个空格隔开,最后也有一个空格;如果为非连通图,再在结尾处另起一行输出一个0,表示此图非连通。
由于广度优先遍历的节点序列是不唯一的,为了使得输出具有唯一的结果,我们约定以表头插入法构造邻接表。
输入样例:
6 8 2
1 2
2 3
3 4
4 5
5 6
6 4
3 6
1 5
输出样例:
2 3 1 6 4 5
代码如下:
#include<bits/stdc++.h>
using namespace std;
vector<int> m[11],now;
bool visited[11];
int N,M,S,cnt=0;
void bfs(){
queue<int> now;
now.push(S);
cout<<S<<" ";
visited[S]=1;
cnt++;
while(now.size()){
queue<int> next;
while(now.size()){
int n=now.front();
now.pop();
for(auto nextnode:m[n]){
if(!visited[nextnode]){
next.push(nextnode);
cout<<nextnode<<" ";
visited[nextnode]=1;
cnt++;
}
}
}
now=next;
}
if(cnt!=N){
cout<<endl;
cout<<0;
}
}
int main(){
cin>>N>>M>>S;
for(int i=0;i<M;i++){
int a,b;
cin>>a>>b;
m[a].push_back(b);
m[b].push_back(a);
}
for(int i=0;i<11;i++)
reverse(m[i].begin(),m[i].end());
bfs();
return 0;
}