题目地址:
https://www.acwing.com/problem/content/370/
银河中的恒星浩如烟海,但是我们只关注那些最亮的恒星。我们用一个正整数来表示恒星的亮度,数值越大则恒星就越亮,恒星的亮度最暗是 1 1 1。现在对于 N N N颗我们关注的恒星,有 M M M对亮度之间的相对关系已经判明。你的任务就是求出这 N N N颗恒星的亮度值总和至少有多大。
输入格式:
第一行给出两个整数
N
N
N和
M
M
M。之后
M
M
M行,每行三个整数
T
,
A
,
B
T,A,B
T,A,B,表示一对恒星
(
A
,
B
)
(A,B)
(A,B)之间的亮度关系。恒星的编号从
1
1
1开始。
如果
T
=
1
T=1
T=1,说明
A
A
A和
B
B
B亮度相等。
如果
T
=
2
T=2
T=2,说明
A
A
A的亮度小于
B
B
B的亮度。
如果
T
=
3
T=3
T=3,说明
A
A
A的亮度不小于
B
B
B的亮度。
如果
T
=
4
T=4
T=4,说明
A
A
A的亮度大于
B
B
B的亮度。
如果
T
=
5
T=5
T=5,说明
A
A
A的亮度不大于
B
B
B的亮度。
输出格式:
输出一个整数表示结果。若无解,则输出
−
1
−1
−1。
数据范围:
N
≤
100000
,
M
≤
100000
N≤100000,M≤100000
N≤100000,M≤100000
可以用差分约束建图,然后求最长路的方法来做,参考https://blog.csdn.net/qq_46105170/article/details/116776382,但是更好的做法是先求强连通分量,然后按拓扑序递推来做(这里的递推本质上也是求最长路,但是复杂度相比于SPFA更优)。首先建图还是和参考链接里的差分约束做法相同,然后求强连通分量,如果同一个分量内存在正边,那么一定存在正环,则无解;否则的话同一个分量里的所有的边长度都是 0 0 0,即点的亮度都相等。接着只需要按照强连通分量的拓扑序递推最长路即可。代码如下:
#include <cstring>
#include <iostream>
using namespace std;
const int N = 100010, M = 600010;
int n, m;
int h[N], hs[N], e[M], ne[M], w[M], idx;
int dfn[N], low[N], timestamp;
int stk[N], top;
bool in_stk[N];
int id[N], scc_cnt, scc_size[N];
int dist[N];
void add(int h[], int a, int b, int c) {
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}
void tarjan(int u) {
dfn[u] = low[u] = timestamp++;
stk[top++] = u, in_stk[u] = true;
for (int i = h[u]; ~i; i = ne[i]) {
int j = e[i];
if (!dfn[j]) {
tarjan(j);
low[u] = min(low[u], low[j]);
} else if (in_stk[j]) low[u] = min(low[u], dfn[j]);
}
if (dfn[u] == low[u]) {
scc_cnt++;
int y;
do {
y = stk[--top];
in_stk[y] = false;
id[y] = scc_cnt;
scc_size[scc_cnt]++;
} while (y != u);
}
}
int main() {
cin >> n >> m;
memset(h, -1, sizeof h);
memset(hs, -1, sizeof h);
// 亮度最小是1,所以开一个源点0,从0到别的点连一条长1的边
for (int i = 1; i <= n; i++) add(h, 0, i, 1);
while (m--) {
int t, a, b;
scanf("%d%d%d", &t, &a, &b);
if (t == 1) add(h, b, a, 0), add(h, a, b, 0);
else if (t == 2) add(h, a, b, 1);
else if (t == 3) add(h, b, a, 0);
else if (t == 4) add(h, b, a, 1);
else if (t == 5) add(h, a, b, 0);
}
tarjan(0);
bool success = true;
// 枚举所有的边,建缩点后的新图
for (int i = 0; i <= n; i++) {
for (int j = h[i]; ~j; j = ne[j]) {
int k = e[j];
int a = id[i], b = id[k];
if (a == b) {
// 如果某个强连通分量内有长度为正的边,那么必然存在正环,则无解
if (w[j] > 0) {
success = false;
break;
}
} else add(hs, a, b, w[j]);
}
if (!success) break;
}
if (!success) puts("-1");
else {
// 按拓扑序做递推,求最长路
for (int i = scc_cnt; i; i--) {
for (int j = hs[i]; ~j; j = ne[j]) {
int k = e[j];
dist[k] = max(dist[k], dist[i] + w[j]);
}
}
long res = 0;
// 总亮度是每个连通分量的亮度乘以连通分量点数求总和
for (int i = 1; i <= scc_cnt; i++) res += (long) dist[i] * scc_size[i];
printf("%ld", res);
}
return 0;
}
时间复杂度 O ( n + m ) O(n+m) O(n+m)( n n n和 m m m分别是图的点数和边数),空间 O ( n ) O(n) O(n)。