7-1 图的深度优先搜索I
无向图 G 有 n 个顶点和 m 条边。求图G的深度优先搜索树(森林)以及每个顶点的发现时间和完成时间。每个连通分量从编号最小的结点开始搜索,邻接顶点选择顺序遵循边的输入顺序。
在搜索过程中,第一次遇到一个结点,称该结点被发现;一个结点的所有邻接结点都搜索完,该结点的搜索被完成。深度优先搜索维护一个时钟,时钟从0开始计数,结点被搜索发现或完成时,时钟计数增1,然后为当前结点盖上时间戳。一个结点被搜索发现和完成的时间戳分别称为该结点的发现时间和完成时间
输入格式:
第1行,2个整数n和m,用空格分隔,分别表示顶点数和边数, 1≤n≤50000, 1≤m≤100000.
第2到m+1行,每行两个整数u和v,用空格分隔,表示顶点u到顶点v有一条边,u和v是顶点编号,1≤u,v≤n.
输出格式:
第1到n行,每行两个整数di和fi,用空格分隔,表示第i个顶点的发现时间和完成时间1≤i≤n 。
第n+1行,1个整数 k ,表示图的深度优先搜索树(森林)的边数。
第n+2到n+k+1行,每行两个整数u和v,表示深度优先搜索树(森林)的一条边<u,v>,边的输出顺序按 v 结点编号从小到大。
输入样例:
在这里给出一组输入。例如:
6 5
1 3
1 2
2 3
4 5
5 6
输出样例:
在这里给出相应的输出。例如:
1 6
3 4
2 5
7 12
8 11
9 10
4
3 2
1 3
4 5
5 6
思路:
使用递归版dfs解题,第一次遇到结点时时间+1,访问结束后时间+1,用两个数组分别保存开始和结束的时间。对于路径,使用sort排序按v从小到大排序。(endl的内存大,还是用\n吧)
代码:
#include<iostream>
#include<vector>
#include<algorithm>
#define maxn 100001
#define max 50001
using namespace std;
vector<int>edges[maxn];
struct Tree {
int u,v;
}tree[maxn];
int n, m,cnt=0 ,tmp=0;
int vist[max]={0};
int begi[max];
int over[max];
void dfs(int x) {
begi [x] = ++cnt;
vist[x] = 1;
for (int i = 0; i < edges[x].size(); i++) {
if (vist[edges[x][i]] != 1) {
tmp++;
tree[tmp].u = x;
tree[tmp].v = edges[x][i];
dfs(edges[x][i]);
}
}
over[x] = ++cnt;
}
bool cmp(Tree a, Tree b) {
return a.v < b.v;
}
int main() {
cin >> n >> m;
for (int i = 1; i <= m; i++) {
int u, v;
cin >> u >> v;
edges[u].push_back(v);
edges[v].push_back(u);
}
for (int i = 1; i <= n; i++) {
if (!vist[i])dfs(i);
}
for (int i = 1; i <= n; i++) {
cout << begi[i] << " " << over[i]<<"\n";
}
cout << tmp<<"\n";
sort(tree + 1, tree + tmp + 1, cmp);
for (int i = 1; i <= tmp; i++) {
cout << tree[i].u << " " << tree[i].v <<"\n";
}
}
7-2 数字变换
利用变换规则,一个数可以变换成另一个数。变换规则如下:(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
思路:
对于每一个x将x+1,x-1,2x分别保存在数组中,并打标记,对于打好标记的的不在保存,时间复杂度为O(1)。再找到y的数后,y对应的数组里保存的上一个的位置。将他们保存在栈里,然后输出。注意要控制num的大小不能超区间会出现断错误。
代码:
#include<iostream>
#include<queue>
#include<stack>
using namespace std;
#define maxn 100001
int vist[maxn];
int path[maxn];
int sum = 0,x,y;
queue<int>q;
stack<int> s;
void bfs() {
q.push(x);
vist[x] = 1;
while (!q.empty()) {
int v = q.front();
q.pop();
for (int i = 1; i <= 3; i++) {
int num = 0;
if (i == 1) num = v + 1;
if (i == 2) num = 2 * v;
if (i == 3) num = v - 1;
if (!vist[num]&&num<maxn&&num>=1) {
vist[num] = 1;
path[num] = v;
q.push(num);
if (num == y) {
return;
}
}
}
}
}
int main() {
cin >> x >> y;
if (x == y)cout << "0";
else {
bfs();
int cnt = 0;
while (y != x) {
s.push(y);
y = path[y];
cnt++;
}
cout << cnt << endl;
for (int i = 1; i <= cnt; i++) {
cout << s.top();
if (i != cnt)cout << " ";
s.pop();
}
}
}
7-3 修轻轨
长春市有n个交通枢纽,计划在1号枢纽到n号枢纽之间修建一条轻轨。轻轨由多段隧道组成,候选隧道有m段。每段候选隧道只能由一个公司施工,施工天数对各家公司一致。有n家施工公司,每家公司同时最多只能修建一条候选隧道。所有公司可以同时开始施工。请评估:修建这条轻轨最少要多少天。。
输入格式:
第1行,两个整数n和m,用空格分隔,分别表示交通枢纽的数量和候选隧道的数量,1 ≤ n ≤ 100000,1 ≤ m ≤ 200000。
第2行到第m+1行,每行三个整数a、b、c,用空格分隔,表示枢纽a和枢纽b之间可以修建一条双向隧道,施工时间为c天,1 ≤ a, b ≤ n,1 ≤ c ≤ 1000000。
输出格式:
输出一行,包含一个整数,表示最少施工天数。
输入样例:
在这里给出一组输入。例如:
6 6
1 2 4
2 3 4
3 6 7
1 4 2
4 5 5
5 6 6
输出样例:
在这里给出相应的输出。例如:
6
思路:
本题考查的是最小生成树。使用kruskal,每次将最短的连接。如果1到n连通时退出加边,则连通分支上最大的权值就是最小的数。这里的并查集优化。
代码:
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
#define maxn 200001
int father[maxn];
int Find(int v) {
if (father[v] <= 0)return v;
return father[v] = Find(father[v]);
}
void Makeset(int x) {
father[x] = 0;
}
void Union(int x, int y) {
int fx = Find(x);
int fy = Find(y);
if (fx == fy)return;
if (father[fx] < father[fy])father[fy] = fx;
else {
if (father[fx] == father[fy])father[fy]--;
father[fx] = fy;
}
}
struct Edge {
int u, v, w;
}edge[maxn], te[maxn];
int n, m;
bool cmp(const Edge& a, const Edge& b) {
return a.w < b.w;
}
void kruskal(int n, int m) {
int cnt = 0,Max=0;
sort(edge + 1, edge + m + 1, cmp);
for (int i = 1; i <= n; i++) {
Makeset(i);
}
for (int i = 1, j = 0; i <= m; i++) {
int u = edge[i].u;
int v = edge[i].v;
if (Find(u) != Find(v)) {
te[++j] = edge[i];
Union(u, v);
}
if(Find(1) == Find(n))break;
if (j == n - 1)break;
cnt = j;
}
for (int i = 1; i <= cnt+1; i++) {
Max = max(Max, te[i].w);
}
cout << Max;
}
int main() {
cin >> n >> m;
int u, v, w;
for (int i = 1; i <= m; i++) {
cin >> u >> v >> w;
edge[i].u = u;
edge[i].v = v;
edge[i].w = w;
}
kruskal(n,m);
}
7-4 发红包
新年到了,公司要给员工发红包。员工们会比较获得的红包,有些员工会有钱数的要求,例如,c1的红包钱数要比c2的多。每个员工的红包钱数至少要发888元,这是一个幸运数字。
公司想满足所有员工的要求,同时也要花钱最少,请你帮助计算。
输入格式:
第1行,两个整数n和m(n<=10000,m<=20000),用空格分隔,分别代表员工数和要求数。
接下来m行,每行两个整数c1和c2,用空格分隔,表示员工c1的红包钱数要比c2多,员工的编号1~n 。
输出格式:
一个整数,表示公司发的最少钱数。如果公司不能满足所有员工的需求,输出-1.
输入样例:
在这里给出一组输入。例如:
2 1
1 2
输出样例:
在这里给出相应的输出。例如:
1777
思路:
本题考查拓扑排序。要求A的红包大于B,则可以先将B的红包发完在发A的红包。等效于拓扑排序。开始时cnt数组等于0的红包大小均为888,然后根据先后顺序依次保存红包大小。如果top=-1,则表示出现环,则表示不满足要求,输出-1。
代码:
#include<iostream>
#include<vector>
using namespace std;
#define maxn 20001
int n, m;
vector<int>edges[maxn];
int cnt[maxn];
int mon[maxn];
int top = -1;
void TopoOrder() {
for (int i = 1; i <= n; i++)cnt[i] = 0;
for (int i = 1; i <= n; i++) {
for (int j = 0; j < edges[i].size(); j++) {
cnt[edges[i][j]]++;
}
}
for (int i = 1; i <= n; i++) {
if (cnt[i] == 0) {
cnt[i] = top;
top = i;
mon[i] = 888;
}
}
for (int i = 1; i <= n; i++) {
if (top == -1) {
cout << "-1";
return;
}
int j = top;
top = cnt[top];
for (int k = 0; k < edges[j].size(); k++) {
int u = edges[j][k];
cnt[u]--;
if (cnt[u] == 0) {
cnt[u] = top;
top = u;
mon[u] = mon[j] + 1;
}
}
}
int sum = 0;
for (int i = 1; i <= n; i++) {
sum += mon[i];
}
cout << sum;
}
int main() {
cin >> n >> m;
for (int i = 1; i <= m; i++) {
int u, v;
cin >> u >> v;
edges[u].push_back(v);
}
TopoOrder();
}