题目地址:
https://www.luogu.com.cn/problem/P3275
题目描述:
幼儿园里有
N
N
N个小朋友,
lxhgww
\text{lxhgww}
lxhgww老师现在想要给这些小朋友们分配糖果,要求每个小朋友都要分到糖果。但是小朋友们也有嫉妒心,总是会提出一些要求,比如小明不希望小红分到的糖果比他的多,于是在分配糖果的时候,
lxhgww
\text{lxhgww}
lxhgww需要满足小朋友们的
K
K
K个要求。幼儿园的糖果总是有限的,
lxhgww
\text{lxhgww}
lxhgww想知道他至少需要准备多少个糖果,才能使得每个小朋友都能够分到糖果,并且满足小朋友们所有的要求。
输入格式:
输入的第一行是两个整数
N
N
N,
K
K
K。接下来
K
K
K行,表示这些点需要满足的关系,每行
3
3
3个数字,
X
X
X,
A
A
A,
B
B
B。
如果
X
=
1
X=1
X=1,表示第
A
A
A个小朋友分到的糖果必须和第
B
B
B个小朋友分到的糖果一样多;
如果
X
=
2
X=2
X=2,表示第
A
A
A个小朋友分到的糖果必须少于第
B
B
B个小朋友分到的糖果;
如果
X
=
3
X=3
X=3,表示第
A
A
A个小朋友分到的糖果必须不少于第
B
B
B个小朋友分到的糖果;
如果
X
=
4
X=4
X=4,表示第
A
A
A个小朋友分到的糖果必须多于第
B
B
B个小朋友分到的糖果;
如果
X
=
5
X=5
X=5,表示第
A
A
A个小朋友分到的糖果必须不多于第
B
B
B个小朋友分到的糖果;
输出格式:
输出一行,表示
lxhgww
\text{lxhgww}
lxhgww老师至少需要准备的糖果数,如果不能满足小朋友们的所有要求,就输出
−
1
-1
−1。
数据范围:
对于
30
%
30\%
30%的数据,保证
N
≤
100
N\leq100
N≤100
对于
100
%
100\%
100%的数据,保证
N
≤
100000
N\leq100000
N≤100000
对于所有的数据,保证
K
≤
100000
,
1
≤
X
≤
5
,
1
≤
A
,
B
≤
N
K\leq100000, 1\leq X\leq5, 1\leq A, B\leq N
K≤100000,1≤X≤5,1≤A,B≤N
标准做法是用最长路求差分约束,这里还可以用缩点 + 拓扑排序来做。先按差分约束最长路的手法建图(该图里每条边都非负),然后在只考虑 0 0 0边的情况下缩点,这样每个团的糖果数应该相等。接着建出缩点后的图,如果某个团内部有非零边,则无解;否则可以利用拓扑排序来求最长路,然后累加起来即可。代码如下:
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int N = 1e5 + 10, M = 4e5 + 10;
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, sz[N];
int ind[N], dist[N];
queue<int> q;
void add(int a, int b, int c, int h[]) {
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]) {
if (w[i]) continue;
int v = e[i];
if (!dfn[v]) {
tarjan(v);
low[u] = min(low[u], low[v]);
} else if (in_stk[v]) low[u] = min(low[u], dfn[v]);
}
if (dfn[u] == low[u]) {
scc_cnt++;
int y;
do {
y = stk[--top];
in_stk[y] = false;
id[y] = scc_cnt;
sz[scc_cnt]++;
} while (y != u);
}
}
int main() {
memset(h, -1, sizeof h);
memset(hs, -1, sizeof hs);
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) add(0, i, 0, h);
while (m--) {
int op, a, b;
scanf("%d%d%d", &op, &a, &b);
// 这里提前特判一下无解的情形
if (a == b) {
if (op == 2 || op == 4) {
puts("-1");
return 0;
}
continue;
}
if (op == 1) add(a, b, 0, h), add(b, a, 0, h);
else if (op == 2) add(a, b, 1, h);
else if (op == 3) add(b, a, 0, h);
else if (op == 4) add(b, a, 1, h);
else add(a, b, 0, h);
}
// 缩点
for (int i = 1; i <= n; i++)
if (!dfn[i])
tarjan(i);
// 建立缩点后的图
for (int i = 1; i <= n; i++)
for (int j = h[i]; ~j; j = ne[j]) {
int x = id[i], y = id[e[j]];
if (x != y) {
ind[y]++;
add(x, y, w[j], hs);
} else if (w[j]) {
puts("-1");
return 0;
}
}
// 拓扑排序
for (int i = 1; i <= scc_cnt; i++)
if (!ind[i]) {
q.push(i);
dist[i] = 1;
}
n = 0;
while (q.size()) {
n++;
int t = q.front(); q.pop();
for (int i = hs[t]; ~i; i = ne[i]) {
int v = e[i];
dist[v] = max(dist[v], dist[t] + w[i]);
ind[v]--;
if (!ind[v]) q.push(v);
}
}
// 有环,说明无解
if (n < scc_cnt) puts("-1");
else {
long res = 0;
for (int i = 1; i <= scc_cnt; i++) res += (long) sz[i] * dist[i];
printf("%ld\n", res);
}
return 0;
}
时间复杂度 O ( N + K ) O(N+K) O(N+K),空间 O ( N ) O(N) O(N)。