一些做題記錄

星影流灿發表於2024-08-11

圖論

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;
}