卡码网题目链接(ACM模式)
题目描述
树可以看成是一个图(拥有 n 个节点和 n -1 条边的连通无环无向图)。
现给定一个拥有 n 个节点(节点编号从 1 到 n)和 n 条边的连通无向图,请找出一条可以删除的边,删除后图可以变成一棵树。
输入描述
第一行包含一个整数 N,表示图的节点个数和边的个数。
后续 N 行,每行包含两个整数 s 和 t,表示图中 s 和 t 之间有一条边。
输出描述
输出一条可以删除的边。如果有多个答案,请删除标准输入中最后出现的那条边。
输入示例
3122313
输出示例
13
提示信息
图中的 12,23,13 等三条边在删除后都能使原图变为一棵合法的树。但是 13 由于是标准输出里最后出现的那条边,所以输出结果为 13
数据范围:
1<= N <=1000.
思考
这道题目也是并查集基础题目。
这里我依然降调一下,并查集可以解决什么问题:两个节点是否在一个集合,也可以将两个节点添加到一个集合中。
如果还不了解并查集,可以看这里:并查集理论基础
题目说是无向图,返回一条可以删去的边,使得结果图是一个有着N个节点的树(即:只有一个根节点)。
如果有多个答案,则返回二维数组中最后出现的边。
那么我们就可以从前向后遍历每一条边(因为优先让前面的边连上),边的两个节点如果不在同一个集合,就加入集合(即:同一个根节点)。
节点A 和节点 B 不在同一个集合,那么就可以将两个 节点连在一起。
0/ \
A B
如果边的两个节点已经出现在同一个集合里,说明着边的两个节点已经连在一起了,再加入这条边一定就出现环了。
如图所示:
0/ \
A --X-- B
已经判断 节点A 和 节点B 在在同一个集合(同一个根),如果将 节点A 和 节点B 连在一起就一定会出现环。
这个思路清晰之后,代码就很好写了。
code c++ 1
// 并查集C++代码如下:#include<iostream>#include<vector>usingnamespace std;int n;// 节点数量
vector<int>father(1001,0);// 按照节点大小范围定义数组// 并查集初始化voidinit(){for(int i =0; i <= n;++i){
father[i]= i;}}// 并查集里寻根的过程intfind(int u){return u == father[u]? u : father[u]=find(father[u]);}// 判断 u 和 v是否找到同一个根boolisSame(int u,int v){
u =find(u);
v =find(v);return u == v;}// 将v->u 这条边加入并查集voidjoin(int u,int v){
u =find(u);// 寻找u的根
v =find(v);// 寻找v的根if(u == v)return;// 如果发现根相同,则说明在一个集合,不用两个节点相连直接返回
father[v]= u;}intmain(){int s, t;
cin >> n;init();for(int i =0; i < n; i++){
cin >> s >> t;if(isSame(s, t)){
cout << s <<" "<< t << endl;return0;}else{join(s, t);}}}
code python 1
classSimultaneous_Query:def__init__(self, N):
self.father =list(range(N +1))deffind(self, u):if u == self.father[u]:return u
else:
self.father[u]= self.find(self.father[u])return self.father[u]defjoin(self, u, v):
u = self.find(self.father[u])
v = self.find(self.father[v])if u == v:return
self.father[v]= u
defis_same(self, u, v):
u = self.find(self.father[u])
v = self.find(self.father[v])return u == v
defmain():# N = 4# nodes = [[1, 2], [2, 3], [1, 3], [1, 4]]
N =int(input())
nodes =[]for _ inrange(N):
v =[int(i)for i ininput().split(' ')]
nodes.append(v)
And1 = Simultaneous_Query(N)for node in nodes:
flag = And1.is_same(node[0], node[1])ifnot flag:
And1.join(node[0], node[1])else:print(node[0], end=' ')print(node[1])
main()
code python 2
N =int(input())
father =[0for _ inrange(N +1)]definit():for i inrange(N+1):
father[i]= i
deffind(u):if u == father[u]:return u
else:
father[u]= find(father[u])return father[u]defjoin(u, v):
u = find(u)
v = find(v)if u == v:returnelse:
father[v]= u
defisSame(u, v):
u = find(u)
v = find(v)return u == v
init()
nodes =[]for j inrange(N):
v =[int(i)for i ininput().split(' ')]
nodes.append(v)for node in nodes:if isSame(node[0], node[1]):print(node[0], end=' ')print(node[1])else:
join(node[0], node[1])
109. 冗余连接II
卡码网题目链接(ACM模式)
题目描述
有向树指满足以下条件的有向图。该树只有一个根节点,所有其他节点都是该根节点的后继。该树除了根节点之外的每一个节点都有且只有一个父节点,而根节点没有父节点。
有向树拥有 n 个节点和 n -1 条边。
输入一个有向图,该图由一个有着 n 个节点(节点编号 从 1 到 n),n 条边,请返回一条可以删除的边,使得删除该条边之后该有向图可以被当作一颗有向树。
输入描述
第一行输入一个整数 N,表示有向图中节点和边的个数。
后续 N 行,每行输入两个整数 s 和 t,代表 s 节点有一条连接 t 节点的单向边
输出描述
输出一条可以删除的边,若有多条边可以删除,请输出标准输入中最后出现的一条边。
输入示例
3121323
输出示例
23
提示信息
在删除 23 后有向图可以变为一棵合法的有向树,所以输出 23
数据范围:
1<= N <=1000.
思路
N =int(input())
father =[0for _ inrange(N +1)]definit():for i inrange(N+1):
father[i]= i
deffind(u):if u == father[u]:return u
else:
father[u]= find(father[u])return father[u]defjoin(u, v):ifnot isSame(u,v):
father[v]= u
defisSame(u, v):
u = find(u)
v = find(v)return u == v
init()
nodes =[]for j inrange(N):
v =[int(i)for i ininput().split(' ')]
nodes.append(v)for node in nodes:if isSame(node[0], node[1]):print(node[0], end=' ')print(node[1])else:
join(node[0], node[1])
109. 冗余连接II
卡码网题目链接(ACM模式
题目描述
有向树指满足以下条件的有向图。该树只有一个根节点,所有其他节点都是该根节点的后继。该树除了根节点之外的每一个节点都有且只有一个父节点,而根节点没有父节点。有向树拥有 n 个节点和 n -1 条边。
输入一个有向图,该图由一个有着 n 个节点(节点编号 从 1 到 n),n 条边,请返回一条可以删除的边,使得删除该条边之后该有向图可以被当作一颗有向树。
输入描述
第一行输入一个整数 N,表示有向图中节点和边的个数。
后续 N 行,每行输入两个整数 s 和 t,代表 s 节点有一条连接 t 节点的单向边
输出描述
输出一条可以删除的边,若有多条边可以删除,请输出标准输入中最后出现的一条边。
输入示例
3121323
输出示例
23
提示信息
在删除 23 后有向图可以变为一棵合法的有向树,所以输出 23
数据范围:
1<= N <=1000.
#include<iostream>#include<vector>usingnamespace std;int n;
vector<int>father(1001,0);// 初始定义最长长度的图// 并查集初始化voidinit(){for(int i =1; i <= n;++i){
father[i]= i;// 进行初始化}}// 并查集里寻根的过程intfind(int u){return u == father[u]? u : father[u]=find(father[u]);}// 将v->u 这条边加入并查集voidjoin(int u,int v){
u =find(u);
v =find(v);if(u == v)return;
father[v]= u;}// 判断 u 和 v是否找到同一个根boolsame(int u,int v){
u =find(u);
v =find(v);return u == v;}// 在有向图里找到删除的那条边,使其变成树voidgetRemoveEdge(const vector<vector<int>>& edges){init();// 初始化并查集for(int i =0; i < n; i++){// 遍历所有的边if(same(edges[i][0], edges[i][1])){// 构成有向环了,就是要删除的边
cout << edges[i][0]<<" "<< edges[i][1];return;}else{join(edges[i][0], edges[i][1]);}}}// 删一条边之后判断是不是树boolisTreeAfterRemoveEdge(const vector<vector<int>>& edges,int deleteEdge){init();// 初始化并查集for(int i =0; i < n; i++){if(i == deleteEdge)continue;if(same(edges[i][0], edges[i][1])){// 构成有向环了,一定不是树returnfalse;}join(edges[i][0], edges[i][1]);}returntrue;}intmain(){int s, t;
vector<vector<int>> edges;
cin >> n;
vector<int>inDegree(n +1,0);// 记录节点入度for(int i =0; i < n; i++){
cin >> s >> t;
inDegree[t]++;
edges.push_back({s, t});}
vector<int> vec;// 记录入度为2的边(如果有的话就两条边)// 找入度为2的节点所对应的边,注意要倒序,因为优先删除最后出现的一条边for(int i = n -1; i >=0; i--){if(inDegree[edges[i][1]]==2){
vec.push_back(i);}}if(vec.size()>0){// 放在vec里的边已经按照倒叙放的,所以这里就优先删vec[0]这条边if(isTreeAfterRemoveEdge(edges, vec[0])){
cout << edges[vec[0]][0]<<" "<< edges[vec[0]][1];}else{
cout << edges[vec[1]][0]<<" "<< edges[vec[1]][1];}return0;}// 处理情况三// 明确没有入度为2的情况,那么一定有有向环,找到构成环的边返回就可以了getRemoveEdge(edges);}
code python 1
father = [0 for _ in range(1001)]
def init():
for i in range(1001): # 初始化并查集
father[i] = i
def find(u): # 并查集里寻根的过程
if u == father[u]:return u
else:
father[u] = find(father[u])
return father[u]
def join(u, v): # 将v->u 这条边加入并查集
u = find(u)
v = find(v)
if u == v:return
else:
father[v] = u
def isSame(u, v): # 判断 u 和 v是否找到同一个根
u = find(u)
v = find(v)
return u == v
# 删一条边之后判断是不是树
def isTreeAfterRemovEdge(edges, deleteEdge):
init() # 初始化并查集
for i in range(len(edges)):
if i == deleteEdge:continue
if isSame(edges[i][0], edges[i][1]): # 构成环之后, 一定不是树
return False
join(edges[i][0], edges[i][1])
return True
# 在有向图里找到删除的那条边,使其变成树
def getRemoveEdge(edges):
init() # 初始化并查集
for i in range(len(edges)): # 遍历所有的边
if isSame(edges[i][0], edges[i][1]): # 构成环了就要删除
print(edges[i][0], end=' ')
print(edges[i][1])
return
join(edges[i][0], edges[i][1])
def main():
init()
n = int(input())
edges = []
isDegree =[0 for _ in range(n + 1)]
for i in range(n):
s, t = [int(i) for i in input().split(' ')]
isDegree[t] += 1 # 记录每个节点入度的个数
edges.append([s, t])
vec = [] # 是否有度为 2 的边 记录入度为2的边(如果有的话就两条边)
for i in range(n-1, -1, -1): # 找入度为2的节点所对应的边,注意要倒序,因为优先删除最后出现的一条边
if isDegree[edges[i][1]] == 2:
vec.append(i)
if len(vec) > 0: # 有入度为2的节点
if isTreeAfterRemovEdge(edges, vec[0]): # 放在vec里的边已经按照倒叙放的,所以这里就优先删vec[0]这条边
print(edges[vec[0]][0], end = ' ')
print(edges[vec[0]][1])
else:
print(edges[vec[1]][0], end = ' ')
print(edges[vec[1]][1])
# 处理情况三
# // 明确没有入度为2的情况,那么一定有有向环,找到构成环的边返回就可以了
else:
getRemoveEdge(edges)
main()
#include<iostream>#include<vector>#include<climits>usingnamespace std;intmain(){int v, e;int x, y, k;
cin >> v >> e;
vector<vector<int>>grid(v +1,vector<int>(v +1,10001));while(e--){
cin >> x >> y >> k;
grid[x][y]= k;
grid[y][x]= k;}
vector<int>minDist(v +1,10001);
vector<bool>isInTree(v +1,false);//加上初始化
vector<int>parent(v +1,-1);for(int i =1; i < v; i++){int cur =-1;int minVal = INT_MAX;for(int j =1; j <= v; j++){if(!isInTree[j]&& minDist[j]< minVal){
minVal = minDist[j];
cur = j;}}
isInTree[cur]=true;for(int j =1; j <= v; j++){if(!isInTree[j]&& grid[cur][j]< minDist[j]){
minDist[j]= grid[cur][j];
parent[j]= cur;// 记录边}}}// 输出 最小生成树边的链接情况for(int i =1; i <= v; i++){
cout << i <<"->"<< parent[i]<< endl;}}
code python 3
defmain():
v, e =[int(v)for v ininput().split(' ')]
grid =[[10001for _ inrange(v +1)]for _ inrange(v +1)]for _ inrange(e):
x, y, val =[int(v)for v ininput().split(' ')]
grid[x][y]= val
grid[y][x]= val
# minDist
minDist =[10001for _ inrange(v +1)]
isTree =[Falsefor _ inrange(v +1)]
parent =[-1for _ inrange(v +1)]for i inrange(1, v):
cur =-1
minVal =float('Inf')for j inrange(1, v +1):if(not isTree[j]and minDist[j]< minVal):
minVal = minDist[j]
cur = j
isTree[cur]=Truefor j inrange(1, v +1):if(not isTree[j]and grid[cur][j]< minDist[j]):
minDist[j]= grid[cur][j]
parent[j]= cur
result =0for i inrange(2, v +1):
result += minDist[i]print(result)for i inrange(1, v +1):if parent[i]!=-1:print(f'{i} -> {parent[i]}')
n = 1000
father = [0 for i in range(n+1)] # 节点数量 并查集标记节点关系的数组 节点编号是从1开始的,n要大一些
# 并查集初始化
def init():
for i in range(n+1):
father[i] = i
# 并查集的查找操作
def find(u):
if father[u] == u: return u
else:
father[u] = find(father[u])
return father[u]
# 并查集的加入集合
def join(u, v):
u = find(u) # 寻找u的根
v = find(v) # 寻找v的根
if u == v: return # 如果发现根相同,则说明在一个集合,不用两个节点相连直接返回
father[v] = u
init()
v, e = 7, 11
edges = [[1, 2, 1], [1, 3, 1], [1, 5, 2], [2, 6, 1], [2, 4, 2], [2, 3, 2], [3, 4, 1], [4, 5, 1], [5, 6, 2], [5, 7, 1], [6, 7, 1]]
edges.sort(key=lambda x:x[2])
result = 0
for edge in edges:
v1 = find(edge[0])
v2 = find(edge[1])
if v1 != v2:
result += edge[2]
join(v1, v2)
def main():
v, e = [int(v) for v in input().split(' ')]
result_val = 0
edges = []
for _ in range(e):
v1 = [int(v) for v in input().split(' ')]
edges.append(v1)
# // 执行Kruskal算法
# // 按边的权值对边进行从小到大排序
edges.sort(key = lambda x:x[2])
# 并查集初始化
init()
# 从头开始遍历边
for edge in edges:
# // 并查集,搜出两个节点的祖先
x = find(edge[0])
y = find(edge[1])
# // 如果祖先不同,则不在同一个集合
if (x != y):
result_val += edge[2] # 这条边可以作为生成树的边
join(x, y) # 两个节点加入到同一个集合
print(result_val)
code python 2
n =1000
father =[0for i inrange(n+1)]definit():for i inrange(n+1):
father[i]= i
deffind(u):if father[u]== u:return u
else:
father[u]= find(father[u])return father[u]defjoin(u, v):
u = find(u)
v = find(v)if u == v:return
father[v]= u
init()
v, e =7,11
edges =[[1,2,1],[1,3,1],[1,5,2],[2,6,1],[2,4,2],[2,3,2],[3,4,1],[4,5,1],[5,6,2],[5,7,1],[6,7,1]]
edges.sort(key=lambda x:x[2])
result =0for edge in edges:
v1 = find(edge[0])
v2 = find(edge[1])if v1 != v2:
result += edge[2]
join(v1, v2)print(result)
如果有 有向环怎么办呢?例如这个图:
这个图,我们只能将入度为0 的节点0 接入结果集。
之后,节点1、2、3、4 形成了环,找不到入度为0 的节点了,所以此时结果集里只有一个元素。
那么如果我们发现结果集元素个数 不等于 图中节点个数,我们就可以认定图中一定有 有向环!
这也是拓扑排序判断有向环的方法。
通过以上过程的模拟大家会发现这个拓扑排序好像不难,还有点简单。
# 写代码
理解思想后,确实不难,但代码写起来也不容易。
为了每次可以找到所有节点的入度信息,我们要在初始话的时候,就把每个节点的入度 和 每个节点的依赖关系做统计。
cin >> n >> m;
vector<int> inDegree(n, 0); // 记录每个文件的入度
vector<int> result; // 记录结果
unordered_map<int, vector<int>> umap; // 记录文件依赖关系
while (m--) {
// s->t,先有s才能有t
cin >> s >> t;
inDegree[t]++; // t的入度加一
umap[s].push_back(t); // 记录s指向哪些文件
}
n, m = [int(v) for v in input().split(' ')]
inDegree = [0 for _ in range(n)] # 记录每个文件的入度
umap = {} # 依赖关系
result = [] # 记录结果
for _ in range(m):
s, t = [int(v) for v in input().split(' ')]
inDegree[t] += 1
umap[s] = umap.get(s, []) + [t]
找入度为0 的节点,我们需要用一个队列放存放。
因为每次寻找入度为0的节点,不一定只有一个节点,可能很多节点入度都为0,所以要将这些入度为0的节点放到队列里,依次去处理。
代码如下:
queue<int> que;
for (int i = 0; i < n; i++) {
// 入度为0的节点,可以作为开头,先加入队列
if (inDegree[i] == 0) que.push(i);
}
开始从队列里遍历入度为0 的节点,将其放入结果集。
while (que.size()) {
int cur = que.front(); // 当前选中的节点
que.pop();
result.push_back(cur);
// 将该节点从图中移除
}
from collections import deque
que = deque()
for i in range(n):
if inDegree[i] == 0:
que.append(i)
while que:
cur = que.popleft()
result.append(cur)
if umap[cur]:
for i in umap[cur]:
inDegree[i] -= 1
if inDegree[i] == 0:
que.append(i)
这里面还有一个很重要的过程,如何把这个入度为0的节点从图中移除呢?
首先我们为什么要把节点从图中移除?
为的是将 该节点作为出发点所连接的边删掉。
删掉的目的是什么呢?
要把 该节点作为出发点所连接的节点的 入度 减一。
如果这里不理解,看上面的模拟过程第一步:
这事节点1 和 节点2 的入度为 1。
将节点0删除后,图为这样:
那么 节点0 作为出发点 所连接的节点的入度 就都做了 减一 的操作。
此时 节点1 和 节点 2 的入度都为0, 这样才能作为下一轮选取的节点。
所以,我们在代码实现的过程中,本质是要将 该节点作为出发点所连接的节点的 入度 减一 就可以了,这样好能根据入度找下一个节点,不用真在图里把这个节点删掉。
该过程代码如下:
while (que.size()) {
int cur = que.front(); // 当前选中的节点
que.pop();
result.push_back(cur);
// 将该节点从图中移除
vector<int> files = umap[cur]; //获取cur指向的节点
if (files.size()) { // 如果cur有指向的节点
for (int i = 0; i < files.size(); i++) { // 遍历cur指向的节点
inDegree[files[i]] --; // cur指向的节点入度都做减一操作
// 如果指向的节点减一之后,入度为0,说明是我们要选取的下一个节点,放入队列。
if(inDegree[files[i]] == 0) que.push(files[i]);
}
}
}
最后代码如下:
code c++ 1
#include <iostream>
#include <vector>
#include <queue>
#include <unordered_map>
using namespace std;
int main() {
int m, n, s, t;
cin >> n >> m;
vector<int> inDegree(n, 0); // 记录每个文件的入度
unordered_map<int, vector<int>> umap;// 记录文件依赖关系
vector<int> result; // 记录结果
while (m--) {
// s->t,先有s才能有t
cin >> s >> t;
inDegree[t]++; // t的入度加一
umap[s].push_back(t); // 记录s指向哪些文件
}
queue<int> que;
for (int i = 0; i < n; i++) {
// 入度为0的文件,可以作为开头,先加入队列
if (inDegree[i] == 0) que.push(i);
//cout << inDegree[i] << endl;
}
// int count = 0;
while (que.size()) {
int cur = que.front(); // 当前选中的文件
que.pop();
//count++;
result.push_back(cur);
vector<int> files = umap[cur]; //获取该文件指向的文件
if (files.size()) { // cur有后续文件
for (int i = 0; i < files.size(); i++) {
inDegree[files[i]] --; // cur的指向的文件入度-1
if(inDegree[files[i]] == 0) que.push(files[i]);
}
}
}
if (result.size() == n) {
for (int i = 0; i < n - 1; i++) cout << result[i] << " ";
cout << result[n - 1];
} else cout << -1 << endl;
}
code python 1
from collections import deque
defmain():
n , m =[int(v)for v ininput().split(' ')]
inDegree =[0for _ inrange(n)]# 记录每个文件的入度
umap ={i:[]for i inrange(n)}# 记录文件依赖关系
result =[]# 记录结果for i inrange(m):
s, t =[int(v)for v ininput().split(' ')]# s->t,先有s才能有t
inDegree[t]+=1# t的入度加一
umap[s]= umap.get(s,[])+[t]# 记录s指向哪些文件
que = deque()for i inrange(n):#入度为0的文件,可以作为开头,先加入队列if inDegree[i]==0:
que.append(i)while que:
cur = que.popleft()# 当前选中的文件
result.append(cur)if umap[cur]:# cur有后续文件 # 获取该文件指向的文件for i in umap[cur]:
inDegree[i]-=1# cur的指向的文件入度-1if inDegree[i]==0:
que.append(i)iflen(result)== n:for i in result:print(i, end=' ')print('\n')else:print(-1)
main()
dijkstra(朴素版)精讲
卡码网:47. 参加科学大会
【题目描述】
小明是一位科学家,他需要参加一场重要的国际科学大会,以展示自己的最新研究成果。
小明的起点是第一个车站,终点是最后一个车站。然而,途中的各个车站之间的道路状况、交通拥堵程度以及可能的自然因素(如天气变化)等不同,这些因素都会影响每条路径的通行时间。
小明希望能选择一条花费时间最少的路线,以确保他能够尽快到达目的地。
【输入描述】
第一行包含两个正整数,第一个正整数 N 表示一共有 N 个公共汽车站,第二个正整数 M 表示有 M 条公路。
接下来为 M 行,每行包括三个整数,S、E 和 V,代表了从 S 车站可以单向直达 E 车站,并且需要花费 V 单位的时间。
【输出描述】
输出一个整数,代表小明从起点到终点所花费的最小时间。
输入示例
79121134232245342453264574679
输出示例:12
【提示信息】
能够到达的情况:
如下图所示,起始车站为 1 号车站,终点车站为 7 号车站,绿色路线为最短的路线,路线总长度为 12,则输出 12。
不能到达的情况:
如下图所示,当从起始车站不能到达终点车站时,则输出 -1。
数据范围:
1<= N <=500;1<= M <=5000;
108. 冗余连接卡码网题目链接(ACM模式)题目描述树可以看成是一个图(拥有 n 个节点和 n - 1 条边的连通无环无向图)。现给定一个拥有 n 个节点(节点编号从 1 到 n)和 n 条边的连通无向图,请找出一条可以删除的边,删除后图可以变成一棵树。输入描述第一行包含一个整数 N,表示图的节点个数和边的个数。后续 N 行,每行包含两个整数 s 和 t,表示图中 s 和 t 之间有一条边。输出描述输出一条可以删除的边。如果有多个答案,请删除标准输入中最后出现的那条边。输入示例31