Description
Hint
(1le N, Kle 10^5)
Soluton 1
构建 差分约束系统。
约定,(u ightarrow v) 的一条边权为 (w) 的有向边表示 ( ext{val}_v > ext{val}_u + w)(即 (v) 比 (u) 至少大 (w))。
那么对于五种情况,我们可以这样建图(( ext{connect}(u, v, w)) 表示连一条 (u ightarrow v) 的一条边权为 (w) 的边):
- (X = 1),( ext{connect}(u, v, 0), ext{connect}(v, u, 0)),表示两点的值大小相同。
- (X = 2),( ext{connect}(u, v, 1)),表示 (v) 比 (u) 至少大 (1)。
- (X = 3),( ext{connect}(v, u, 0)),表示 (u) 大于等于 (v)。
- (X = 4),( ext{connect}(v, u, 1)),表示 (u) 比 (v) 至少大 (1)。
- (X = 5),( ext{connect}(u, v, 0)),表示 (v) 大于等于 (u)。
最后以 (n + 1) 为超级源点,分别向结点 (1cdots n) 建一条权为 (0) 边,整个图就建好了。
接下来,从 (n + 1) 开始,用 ( exttt{Spfa}) 跑 最长路(注意特判正环)。
为什么不是最短路?因为我们必须确保满足最多的约束条件才行。
- 注意点:
- 当 (X in {2,4}) 时,如果 (u = v),那么直接输出 (-`)。
- 不开
long long
见祖宗。
时间复杂度最坏 (O(nm)),但是由于数据水所以过了。
Code for solution 1
/*
* Author : _Wallace_
* Source : https://www.cnblogs.com/-Wallace-/
* Problem : Luogu P3275 SCOI2011 糖果
*/
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
using namespace std;
const int N = 1e5 + 5;
struct edge {
int p, w;
};
vector<edge> G[N];
int n, m;
inline void connect(int u, int v, int w) {
G[u].push_back(edge{v, w});
}
queue<int> Q;
int cnt[N], dis[N];
bool inque[N];
long long spfa(int s) {
memset(cnt, 0, sizeof cnt);
memset(inque, 0, sizeof inque);
memset(dis, 0, sizeof dis);
inque[s] = true, Q.push(s);
while (!Q.empty()) {
int u = Q.front(); Q.pop();
inque[u] = false;
if (cnt[u]++ == n) return -1ll;
for (auto v : G[u]) if (dis[v.p] < dis[u] + v.w) {
dis[v.p] = dis[u] + v.w;
if (!inque[v.p]) inque[v.p] = true, Q.push(v.p);
}
}
long long ans = 0ll;
for (register int i = 1; i <= n; i++)
ans += dis[i];
return ans;
}
signed main() {
scanf("%d%d", &n, &m);
for (register int i = 1; i <= m; i++) {
int k, u, v;
scanf("%d%d%d", &k, &u, &v);
switch (k) {
case 1 : connect(u, v, 0), connect(v, u, 0); break;
case 2 : if (u == v) return puts("-1"), 0; connect(u, v, 1); break;
case 3 : connect(v, u, 0); break;
case 4 : if (u == v) return puts("-1"), 0; connect(v, u, 1); break;
case 5 : connect(u, v, 0); break;
}
}
for (register int i = 1; i <= n; i++)
connect(n + 1, i, 1);
printf("%lld
", spfa(n + 1));
}
Solution 2
上面的算法虽然简单但是不稳定,可能会被卡。这里介绍一种稳定而且更优秀的算法。
首先,对于所有 (Xin {1, 3, 5}) 的约束条件(因为含有容许相等的关系,可以缩点),我们先连边:如果 (Ale B),则连上一条 (A ightarrow B) 的边,并标记为边权为 0(就是一个记号)。
然后可以 Tarjan 缩点一下,并把缩点后的新图(( ext{Gx}))构造好。
然后再找出所有 (Xin {2,4}) 的约束条件,如果发现在新图上产生了自环(即 ( ext{belong}_A = ext{belong}_B)),那么显然有矛盾出现,输出 (-1) 跑路。如果没有自环,那就在 ( ext{Gx}) 上连边:如果 (A<B),那就连边 (A ightarrow B),边权为 (1)。注意这里的 (A,B) 都是 缩点后 的位置。
然后就可以直接dp了。然后有两种写法:
-
使用拓扑排序,做的时侯顺便 dp:如果边权为 (0),那么在取 (max) 的时候需要将前驱结点的 dp 值 (+1);反之则不用 (+1)。至于为什么是取 (max),这个和上面最长路的解释差不多。如果最后做下来发现了环,那就输出 (-1)。
-
建反边,Dfs 判环,记忆化搜索:将 ( ext{Gx}) 的边都反着建,然后就可以记搜了。注意做之前需要判断是不是 DAG,这个可以 Dfs 实现。
算法时间复杂度:(O(n + m)),但由于数据过水以及常数因子的影响优势不大。
Code for solution 2
拓扑排序写法
/*
* Author : _Wallace_
* Source : https://www.cnblogs.com/-Wallace-/
* Problem : Luogu P3275 SCOI2011 糖果
*/
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>
#include <queue>
#include <stack>
using namespace std;
const int N = 1e5 + 5;
struct edge {
int p, w;
};
vector<edge> G[N], Gx[N];
int n, m;
int X[N], A[N], B[N];
inline void connect1(int u, int v, int w) {
G[u].push_back(edge{v, w});
}
inline void connect2(int u, int v, int w) {
Gx[u].push_back(edge{v, w});
}
int dfn[N], low[N];
int belong[N], size[N], scc = 0;
int timer = 0;
stack<int> st;
bool inst[N];
void Tarjan(int x) {
dfn[x] = low[x] = ++timer;
st.push(x), inst[x] = true;
for (auto y : G[x])
if (!dfn[y.p]) Tarjan(y.p), low[x] = min(low[x], low[y.p]);
else if (inst[y.p]) low[x] = min(low[x], dfn[y.p]);
if (low[x] == dfn[x]) for (++scc; ; ) {
int k = st.top(); st.pop();
belong[k] = scc, size[scc]++, inst[k] = false;
if (k == x) break;
}
}
int num[N], in[N];
bool del[N];
queue<int> que;
void calc() {
for (register int i = 1; i <= scc; i++)
for (auto j : Gx[i]) in[j.p]++;
for (register int i = 1; i <= scc; i++)
if (!in[i]) que.push(i), del[i] = true, num[i] = 1;
while (!que.empty()) {
int u = que.front(); que.pop();
for (auto v : Gx[u]) {
if (!--in[v.p])
que.push(v.p), del[v.p] = 1;
if (!v.w) num[v.p] = max(num[v.p], num[u] + 1);
else num[v.p] = max(num[v.p], num[u]);
}
}
for (register int i = 1; i <= scc; i++)
if (!del[i]) puts("-1"), exit(0);
}
signed main() {
scanf("%d%d", &n, &m);
for (register int i = 1; i <= m; i++)
scanf("%d%d%d", X + i, A + i, B + i);
/*u -(0/1)-> v : u <(=) v*/
for (register int i = 1; i <= n; i++)
switch (X[i]) {
case 1 : connect1(A[i], B[i], 1), connect1(B[i], A[i], 1); break;
case 3 : connect1(B[i], A[i], 1); break;
case 5 : connect1(A[i], B[i], 1); break;
}
for (register int i = 1; i <= n; i++)
if (!dfn[i]) Tarjan(i);
for (register int i = 1; i <= n; i++)
for (auto j : G[i]) if (belong[i] != belong[j.p])
connect2(belong[i], belong[j.p], 1);
for (register int i = 1; i <= m; i++)
if (X[i] == 2) {
if (belong[A[i]] == belong[B[i]])
return puts("-1"), 0;
connect2(belong[A[i]], belong[B[i]], 0);
} else if (X[i] == 4) {
if (belong[A[i]] == belong[B[i]])
return puts("-1"), 0;
connect2(belong[B[i]], belong[A[i]], 0);
}
calc();
long long ans = 0ll;
for (register int i = 1; i <= scc; i++)
ans += num[i] * 1LL * size[i];
printf("%lld", ans);
return 0;
}
记忆化搜索写法
/*
* Author : _Wallace_
* Source : https://www.cnblogs.com/-Wallace-/
* Problem : Luogu P3275 SCOI2011 糖果
*/
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>
#include <stack>
using namespace std;
const int N = 1e5 + 5;
struct edge {
int p, w;
};
vector<edge> G[N], Gx[N];
int n, m;
int X[N], A[N], B[N];
inline void connect1(int u, int v, int w) {
G[u].push_back(edge{v, w});
}
inline void connect2(int u, int v, int w) {
Gx[v].push_back(edge{u, w});
}
int dfn[N], low[N];
int belong[N], size[N], scc = 0;
int timer = 0;
stack<int> st;
bool inst[N];
void Tarjan(int x) {
dfn[x] = low[x] = ++timer;
st.push(x), inst[x] = true;
for (auto y : G[x])
if (!dfn[y.p]) Tarjan(y.p), low[x] = min(low[x], low[y.p]);
else if (inst[y.p]) low[x] = min(low[x], dfn[y.p]);
if (low[x] == dfn[x]) for (++scc; ; ) {
int k = st.top(); st.pop();
belong[k] = scc, size[scc]++, inst[k] = false;
if (k == x) break;
}
}
int vis[N];
void judgeRing(int x) {
vis[x] = 1;
for (auto y : Gx[x]) {
if (vis[y.p] == 1) puts("-1"), exit(0);
if (vis[y.p] != -1) judgeRing(y.p);
}
vis[x] = -1;
}
int num[N];
int calc(int x) {
if (num[x]) return num[x];
if (Gx[x].empty()) return num[x] = 1;
for (auto y : Gx[x])
if (!y.w) num[x] = max(num[x], calc(y.p) + 1);
else num[x] = max(num[x], calc(y.p));
return num[x];
}
signed main() {
scanf("%d%d", &n, &m);
for (register int i = 1; i <= m; i++)
scanf("%d%d%d", X + i, A + i, B + i);
/*u -(0/1)-> v : u <(=) v*/
for (register int i = 1; i <= n; i++)
switch (X[i]) {
case 1 : connect1(A[i], B[i], 1), connect1(B[i], A[i], 1); break;
case 3 : connect1(B[i], A[i], 1); break;
case 5 : connect1(A[i], B[i], 1); break;
}
for (register int i = 1; i <= n; i++)
if (!dfn[i]) Tarjan(i);
for (register int i = 1; i <= n; i++)
for (auto j : G[i]) if (belong[i] != belong[j.p])
connect2(belong[i], belong[j.p], 1);
for (register int i = 1; i <= m; i++)
if (X[i] == 2) {
if (belong[A[i]] == belong[B[i]])
return puts("-1"), 0;
connect2(belong[A[i]], belong[B[i]], 0);
} else if (X[i] == 4) {
if (belong[A[i]] == belong[B[i]])
return puts("-1"), 0;
connect2(belong[B[i]], belong[A[i]], 0);
}
for (register int i = 1; i <= scc; i++)
if (~vis[i]) judgeRing(i);
for (register int i = 1; i <= scc; i++)
calc(i);
long long ans = 0ll;
for (register int i = 1; i <= scc; i++)
ans += num[i] * 1LL * size[i];
printf("%lld", ans);
return 0;
}