圖論
P1993 小 K 的農場
複習差分約束QAQ。設農場 \(i\) 種植的作文有 \(x_i\) 個單位,則題目中的三個條件就是:
\[\begin{cases}
x_a \ge x_b + c \\
x_a \le x_b + c \\
x_a = x_b
\end{cases}
\]
其中第一個不等式可以轉化成
\[\begin{cases}
x_b \le x_a - c
\end{cases}
\]
注意到最短路中對於任何節點 \(x\) ,都滿足 \(dis_x \le dis_y + w_{y, x}\) ,所以我們只需要對於不等式一,從 \(a\) 向 \(b\) 連一條長度為 \(-c\) 的邊,對於不等式二,從 \(b\) 向 \(a\) 連一條長度為 \(c\) 的邊,對於不等式三,因為 \(a = b \Leftrightarrow a \le b\) 且 \(b \le a\) ,所以我們從 \(a\) 向 \(b\) 連一條長度為 \(0\) 的邊,再從 \(b\) 向 \(a\) 連一條長度為 \(0\) 的邊,最後跑一遍最短路即可。需要注意,有可能圖中出現負環,判無解即可。
#include<bits/stdc++.h>
using namespace std;
#define N 6010
#define pii pair<int, int>
int n, m;
int vis[N], cnt[N], d[N];
vector<pii> e[N];
int spfa(int s){
queue<pii> q;
q.push({s, 0});
memset(d, 0x3f, sizeof d);
vis[s] = 1, d[s] = 0;
while(!q.empty()){
int u = q.front().first; q.pop();
vis[u] = 0;
for(auto v : e[u]){
if(d[v.first] > d[u] + v.second){
d[v.first] = d[u] + v.second;
cnt[v.first] = cnt[u] + 1;
if(cnt[v.first] >= 2 * n) return 0;
if(!vis[v.first]){
vis[v.first] = 1;
q.push({v.first, d[v.first]});
}
}
}
}
return 1;
}
signed main(){
// freopen("sr.in", "r", stdin);
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> n >> m;
while(m--){
int op, a, b, c;
cin >> op >> a >> b;
if(op == 1){
cin >> c;
e[a].push_back({b, -c});
}
else if(op == 2){
cin >> c;
e[b].push_back({a, c});
}
else{
e[a].push_back({b, 0}), e[b].push_back({a, 0});
}
}
for(int i = 1; i <= n; i ++){
e[n + 1].push_back({i, 0});
}
if(spfa(n + 1)) cout << "Yes" << endl;
else cout << "No" << endl;
return 0;
}
P1195 口袋的天空
一眼最小生成樹。但是題意仍然看了好久才看懂QAQ。就是給定 \(n\) 個點, \(m\) 條邊,連成 \(k\) 個樹的最小代價。容易想到 Kruskal,每一次建邊將當前的連通塊--,直到最後為 \(k\) 個即可。感覺Prim沒什麼應用場景啊 。
#include<bits/stdc++.h>
using namespace std;
#define N 1010
#define M 10010
int n, m, k;
int cnt, ans;
int fa[N];
struct EDGE{
int a, b, w;
}e[M];
int find(int x){
return fa[x] == x ? x : fa[x] = find(fa[x]);
}
int cmp(EDGE a, EDGE b){
return a.w < b.w;
}
signed main(){
// freopen("sr.in", "r", stdin);
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> n >> m >> k;
for(int i = 1; i <= m; i ++){
int a, b, c;
cin >> a >> b >> c;
e[i] = {a, b, c};
}
for(int i = 1; i <= n; i ++){
fa[i] = i;
}
cnt = n;
sort(e + 1, e + m + 1, cmp);
for(int i = 1; i <= m; i ++){
int a = find(e[i].a), b = find(e[i].b);
if(a == b) continue;
cnt--;
fa[a] = b;
ans += e[i].w;
if(cnt == k){
cout << ans;
return 0;
}
}
cout << "No Answer";
return 0;
}
P1550 [USACO08OCT] Watering Hole G
感覺很奇妙的一題。看似圖中只有 \(n\) 個節點,實際上,完全可以將打井看作是向地下的某個隱藏節點連邊。於是就加上這個節點跑最小生成樹就好啦。
我才不會說我寫Kruskal忘排序了呢
#include<bits/stdc++.h>
using namespace std;
#define N 310
#define M 100010
int n, m;
int cnt = 0;
int p[N];
struct EDGE{
int a, b, w;
}e[M];
int find(int x){
return p[x] == x ? x : p[x] = find(p[x]);
}
int cmp(EDGE a, EDGE b){
return a.w < b.w;
}
void Kruskal(){
sort(e + 1, e + cnt + 1, cmp);
int ans = 0;
for(int i = 1; i <= n + 1; i ++) p[i] = i;
for(int i = 1; i <= cnt; i ++){
int a = find(e[i].a), b = find(e[i].b);
if(a == b) continue;
ans += e[i].w;
p[a] = b;
}
cout << ans;
}
signed main(){
//freopen("sr.in", "r", stdin);
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> n;
for(int i = 1; i <= n; i ++){
int w;
cin >> w;
e[++cnt] = {n + 1, i, w};
}
for(int i = 1; i <= n ;i ++){
for(int j = 1; j <= n; j ++){
int w;
cin >> w;
e[++cnt] = {i, j, w};
}
}
Kruskal();
return 0;
}