天天看點

并查集

并查集

​​E. 【例題4】超市購物 - 「圖論」第1章 并查集課堂過關 - 高效進階 - 課程 - YbtOJ​​

又是一道并查集好題,我一開始沒有看出來為什麼要用并查集,我不會告訴你我是看了别人的部落格才會的。用并查集維護時間,如果我們的時間到達了0,那我們就不能再進入操作了接下來看代碼解決。

code:

#include <bits/stdc++.h>
//#define int long long
using namespace std;
inline int read(){
  int x=0,w=1;
  char ch=getchar();
  while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
  while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
  return w*x;
}
int n;
struct node{
  int v,t;
}a[10004];
int fa[10004];
bool cmp(node a,node b){
  return a.v>b.v;
}
int find(int x){
  if(fa[x]==x)
    return x;
  return fa[x]=find(fa[x]);
}
int ans;
int main(){
  n=read();
  for(int i=1;i<=n;i++)
  a[i].v=read(),a[i].t=read();
  for(int i=1;i<=10001;i++) //這裡一定要為10001,不要設成n,因為時間可以比n大(不會告訴你,我鍋了很長時間)
  fa[i]=i;
  sort(a+1,a+1+n,cmp);
  for(int i=1;i<=n;i++)
    {
      int t=find(a[i].t);
      if(t)
      {
        ans+=a[i].v;
        fa[t]=t-1;
      }
    }
  cout<<ans<<endl;
  return 0;
}

      

​​F. 【例題5】逐個擊破 - 「圖論」第1章 并查集課堂過關 - 高效進階 - 課程 - YbtOJ​​

這道題在\(luogu\)上也有原題,但被評為藍題,我覺得有點虛高,應該算一道綠題

閑話不多說,開始:

這道題要我們求删邊的最小值,不好維護,但我們可以運用補集的思想,那就是求我們加邊的最大值。

但這道題有一個限制,就是我們每個區域隻能有一個壞點。這就讓我們想到了要用并查集來維護。

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;

int n, m;
long long ans;
int vis[100010], fa[100010];
struct arr {
    int u, v, w;
} e[100010];

inline int read() {
    int x = 0, w = 1;
    char ch = 0;
    while (ch != '-' && (ch < '0' || ch > '9')) ch = getchar();
    if (ch == '0')
        w = -1, ch = getchar();
    while (ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + (ch - 48), ch = getchar();
    return x * w;
}

int gf(int x) { return x == fa[x] ? x : fa[x] = gf(fa[x]); }
inline int cmp(arr a, arr b) { return a.w > b.w; }

int main() {
    // freopen("shiitake.in","r",stdin);
    // freopen("shiitake.out","w",stdout);
    n = read();
    m = read();
    for (int i = 1; i <= m; i++) {
        int x = read();
        vis[x] = 1;
    }
    for (int i = 1; i < n; i++) {
        int u = read(), v = read(), w = read();
        e[i].u = u;
        e[i].v = v;
        e[i].w = w;
        ans += w;
    }
    sort(e + 1, e + n, cmp);
    for (int i = 1; i <= n; i++) fa[i] = i;
    for (int i = 1; i < n; i++) {
        int f1 = gf(e[i].u);
        int f2 = gf(e[i].v);
        if (!(vis[f1] && vis[f2])) {
            fa[f2] = f1;
            vis[f1] = (vis[f1] || vis[f2]);
            ans -= e[i].w;
        }
    }
    printf("%lld\n", ans);
}

      

​​A. 1.躲避擁擠 - 「圖論」第1章 并查集強化訓練 - 高效進階 - 課程 - YbtOJ​​

躲避擁擠的這道題我覺得要比前面的兩道題都要簡單,可能是T1的原因。前兩道的重點在于如何想到用并查集去維護,而這道題沒有任何含金量,非常簡單,代碼很詳細。

#include <bits/stdc++.h>
using namespace std;
int test, Q, n, m, tot;  // test是資料的組數,Q是每組資料中的詢問次數,n是景點數,m是邊數
int fa[100001];          //記錄其祖先元素
int sum[100001];         //用于維護集合的大小
int ans[100001];         //儲存每次詢問的答案
struct no                //記錄原始資料
{
    int x;  //出發景點
    int y;  //到達景點
    int d;  //人氣值
} a[100001];
struct de  //記錄詢問的資料
{
    int x;    //記錄要求不能超過的人氣值的大小
    int num;  //記錄第幾次詢問
} q[100001];
bool cmp1(no a, no b)  //将原始資料按從小到大的順序排個序
{
    return a.d < b.d;
}
bool cmp2(de a, de b)  //将詢問資料也按從小到大的順序排個序
{
    return a.x < b.x;
}
int find(int x)  //并查集基本操作,尋找該節點的祖先
{
    if (!fa[x]) {
        return x;
    }
    return fa[x] = find(fa[x]);
}
void add(int x, int y)  //并查集基本操作,合并兩個集合,不過要穿差一點東西,以到達維護sum數組的目的
{
    int u = find(x);  //尋找x祖先元素
    int v = find(y);  //尋找y祖先元素
    if (u == v)       //如果兩個元素已在同一個集合,則直接忽略
    {
        return;
    }
    tot += sum[u] * sum[v] * 2;  //否則,這兩個點對答案的貢獻就是sum[u]*sum[v]*2(不要忘記乘2)
    fa[u] = v;                   //合并兩個集合
    sum[v] += sum[u];            //維護sum數組
}
int main() {
    scanf("%d", &test);  //輸入test
    while (test--)       // test次循環
    {
        scanf("%d%d%d", &n, &m, &Q);  //讀入
        for (int i = 1; i <= m; i++)  //讀入
        {
            scanf("%d%d%d", &a[i].x, &a[i].y, &a[i].d);
        }
        sort(a + 1, a + m + 1, cmp1);  //給原始資料排序
        for (int i = 1; i <= Q; i++)   //讀入
        {
            scanf("%d", &q[i].x);  //讀入
            q[i].num = i;          //記錄一下是第幾次詢問
        }
        sort(q + 1, q + 1 + Q, cmp2);  //給詢問資料排序
        tot = 0;                       //記錄景點的對數
        for (int i = 1; i <= n; i++)   //并查集基本操作之一————初始化
        {
            fa[i] = 0;
            sum[i] = 1;  //初始時每個元素都是一個集合,是以sum數組的值都為1
        }
        int j = 1;  //記錄目前詢問到第幾組道路
        for (int i = 1; i <= Q; i++) {
            while ((j <= m) && (a[j].d <= q[i].x)) {
                add(a[j].x, a[j].y);  //合并兩個集合
                j++;                  //詢問數加1
            }
            ans[q[i].num] = tot;  //記錄答案
        }
        for (int i = 1; i <= Q; i++)  //輸出
        {
            printf("%d\n", ans[i]);
        }
    }
    return 0;  //完結撒花
}

      

​​B. 3.數列詢問 - 「圖論」第1章 并查集強化訓練 - 高效進階 - 課程 - YbtOJ​​

自認為這道題要用到數學的一些知識,這道題的問法太并查集了,是以我們知道要用并查集做。

用\(b_i\)表示數組\(a\)的字首和。發現每一組詢問\(l,r,k\)就表示

\[b_r-b_{l-1}=k(mod\ p)\]

我們可以用并查集來維護該關系,并查集中每一條邊維護該點和父親差對\(p\)取模的結果。

如果點\(l-1\)和點\(r\)不在一個并查集,那就将兩者放入一個并查集中并設定邊權。

如果\(l-1\)和點\(r\)在一個并查集,那就判斷兩者之間是否合法。

時間複雜度是\(O(n×α(n))\)。

​​C. 4.染色操作 - 「圖論」第1章 并查集強化訓練 - 高效進階 - 課程 - YbtOJ​​(********************)

這道題很有個性,他考的是區間中的東西,區間一定要想到幾個點,分别是DP,線段樹,ST表,樹狀數組(我不會),這道題确實用線段樹可做,但隻能拿到60分(但我覺得某些大佬一定會用卡常技巧,A掉的)。這道題我們用巧妙的并查集,我們枚舉每個區間的點,這樣就行了,因為每個點最多被通路一次,是以不會T掉。

Tcode:

這份代碼被T掉了,但是更好了解
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <algorithm>
#include <set>
#include <queue>
using namespace std;
const int maxn = 2e6 + 3;
int fa[maxn];
int n, m;
int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }
void init() {
    for (int i = 0; i <= n; i++) {
        fa[i] = i;
    }
}
int main() {
    while (~scanf("%d%d", &n, &m)) {
        init();
        for (int i = 0; i < m; i++) {
            int x, y;
            scanf("%d%d", &x, &y);
            while (find(y) != find(x - 1)) {
                fa[find(y)] = fa[find(y) - 1];
                n--;
            }
            cout << n << endl;
        }
    }
    return 0;
}
      

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 40;
int fa[maxn], n, m, l, r;
int Find(int x) {
    if (fa[x] == x)
        return x;
    return fa[x] = Find(fa[x]);
}
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n + 1; i++) fa[i] = i;
    for (int i = 1; i <= m; ++i) {
        scanf("%d%d", &l, &r);
        if (l > r)
            swap(l, r);
        while (1) {
            l = Find(l);
            if (l > r)
                break;
            else {
                n--;
                fa[l] = l + 1;
            }
        }
        printf("%d\n", n);
    }
    return 0;
}

      

​​D. 5.小明反擊 - 「圖論」第1章 并查集強化訓練 - 高效進階 - 課程 - YbtOJ​​

後面的等學到圖論再講吧...