通过洛谷提单中的四道题进行练习。
P3916https://www.luogu.com.cn/problem/P3916
描述:给出 N 个点,M 条边的有向图,对于每个点 v,求 A(v) 表示从点 v 出发,能到达的编号最大的点。
该题是图最基本的应用,但要注意本题的技巧--反向建边。由于要找到可到达最大编号的值,我们可以反向建边,再从大到小反向遍历。利用vis数组,如果不为零则return因为是从大到小遍历,那么已有数字肯定更大。
#include<iostream>
#include<string>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
using ll = long long;
const int N = 1e5 + 10;
vector<int>g[N];
int vis[N];
void dfs(int x, int y) {//注意这里的x和y变量,y不变
if (vis[x])return;
vis[x] = y;
for (const auto& u : g[x]) {
dfs(u, y);
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr); cout.tie(nullptr);
int n, m; cin >> n >> m;
for (int i = 1; i <= m; i++) {//存图
int x, y;
cin >> x >> y;
g[y].push_back(x);//注意是有向图,且由于要反向建边,我们需要将边的关系反过来
}
for (int i = n; i >= 1; i--) {
dfs(i, i);
}
for (int i = 1; i <= n; i++) {
cout << vis[i] << ' ';
}
return 0;
}
P1113https://www.luogu.com.cn/problem/P1113
描述:John 的农场在给奶牛挤奶前有很多杂务要完成,每一项杂务都需要一定的时间来完成它。比如:他们要将奶牛集合起来,将他们赶进牛棚,为奶牛清洗乳房以及一些其它工作。尽早将所有杂务完成是必要的,因为这样才有更多时间挤出更多的牛奶。
当然,有些杂务必须在另一些杂务完成的情况下才能进行。比如:只有将奶牛赶进牛棚才能开始为它清洗乳房,还有在未给奶牛清洗乳房之前不能挤奶。我们把这些工作称为完成本项工作的准备工作。至少有一项杂务不要求有准备工作,这个可以最早着手完成的工作,标记为杂务 1。
John 有需要完成的 n 个杂务的清单,并且这份清单是有一定顺序的,杂务 k (k>1) 的准备工作只可能在杂务 1 至 k−1 中。
写一个程序依次读入每个杂务的工作说明。计算出所有杂务都被完成的最短时间。当然互相没有关系的杂务可以同时工作,并且,你可以假定 John 的农场有足够多的工人来同时完成任意多项任务。
题目有点长,但细读并不复杂,这题需要我们使用拓扑排序的相关内容。同时,这类题我们不难想到要通过状态转移来得到最后的结果。
#include<iostream>
#include<string>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
using ll = long long;
const int N = 1e4 + 9;
vector<int>g[N];
int ind[N], f[N], a[N], t, ans;//ind记录入度,f记录转移数组,a记录时间
queue<int>q;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr); cout.tie(nullptr);
int n; cin >> n;
for (int i = 1; i <= n; i++) {
cin >> i >> a[i];
while (cin >> t && t) {//前置工作
g[t].push_back(i);
ind[i]++;
}
}
for (int i = 1; i <= n; i++) {
if (ind[i] == 0) {
q.push(i);
f[i] = a[i];
}
}
while (q.size()) {
int x = q.front(); q.pop();
for (const auto& u : g[x]) {
if (--ind[u] == 0) {
q.push(u);
}
f[u] = max(f[u], f[x] + a[u]);
ans = max(ans, f[u]);
}
}
cout << ans;
return 0;
}
P4017https://www.luogu.com.cn/problem/P4017
描述:
给你一个食物网,你要求出这个食物网中最大食物链的数量。
(这里的“最大食物链”,指的是生物学意义上的食物链,即最左端是不会捕食其他生物的生产者,最右端是不会被其他生物捕食的消费者。)
Delia 非常急,所以你只有 1 秒的时间。
由于这个结果可能过大,你只需要输出总数模上 80112002 的结果。
仔细读题,发现这题的要求与基本的拓扑排序不同的是,最后的点需要出度为0。因此,我们再记录入度的同时也要记录出度。
#include<iostream>
#include<string>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
using ll = long long;
const int N = 5e3 + 10;
const int P = 80112002;
vector<int>g[N];
queue<int>q;
int ind[N], dp[N], out[N], ans;//dp数组用来转移路径数量
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr); cout.tie(nullptr);
int n, m; cin >> n >> m;
for (int i = 1; i <= m; i++) {
int x, y; cin >> x >> y;
g[y].push_back(x);
ind[x]++; out[y]++;
}
for (int i = 1; i <= n; i++) {
if (ind[i] == 0) {
q.push(i);
dp[i] = 1;//要注意,当入度为0才用初始化
}
}
while (q.size()) {
int x = q.front(); q.pop();
for (const auto& u : g[x]) {
if (--ind[u] == 0)q.push(u);
dp[u] = (dp[u] + dp[x]) % P;//由于曾经dp[u]获得的值肯定不会结果x,直接加
}
}
for (int i = 1; i <= n; i++) {
if (!out[i])ans = (ans + dp[i]) % P;
}
cout << ans;
return 0;
}
P1807https://www.luogu.com.cn/problem/P1807
描述:设 G 为有 n 个顶点的带权有向无环图,G 中各顶点的编号为 1 到 n,请设计算法,计算图 G 中 1,n 间的最长路径。
要注意,题目要求是1到n的最长路。题目保证u<v,即1的入度为0。而真实情况是可能不知1的入度为0,我们想要用常规的拓扑排序是无法解决的。而想要解决这道题,理想情况是只有1这一个起点。所以我们需要现消除其他的起点及他们带来的影响。有什么影响?由于这些点也是与其他点相连的,所以他们会影响其他点的入度。我们的代码就可以采用两次bfs,第一次仅保留1,第二次得到结果。
#include<iostream>
#include<string>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
using ll = long long;
const int N = 1510;
int ind[N], dp[N];
vector<int>g[N], d[N];//d[N]数组用来存边权值
queue<int>q;
int n, m;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr); cout.tie(nullptr);
cin >> n >> m;
for (int i = 1; i <= m; i++) {
int x, y, z; cin >> x >> y >> z;
g[x].push_back(y);
d[x].push_back(z);
ind[y]++;
}
for (int i = 2; i <= n; i++) {//要注意从2开始,因为我们要保留1
if (ind[i] == 0)q.push(i);
dp[i] = -1e9;//初始化,由于有负权,且根据所给数据,初始化为-1e9
}
while (q.size()) {
int x = q.front(); q.pop();
for (const auto& u : g[x]) {
if (--ind[u] == 0)q.push(u);
}
}
//处理后就剩下1了,下面便简单了
q.push(1);
while (q.size()) {
int x = q.front(); q.pop();
//注意:不能使用auto了(至少我不会)因为要用size来找存的权值
for (int i = 0; i < g[x].size(); i++) {
if (--ind[g[x][i]] == 0)q.push(g[x][i]);
dp[g[x][i]] = max(dp[g[x][i]], dp[x] + d[x][i]);
}
}
if (dp[n] == -1e9)cout << -1;
else cout << dp[n];
return 0;
}