NC20566 [SCOI2010]遊戲

2022-07-16 06:00:18

題目連結

題目

題目描述

lxhgww最近迷上了一款遊戲,在遊戲裡,他擁有很多的裝備,每種裝備都有2個屬性,這些屬性的值用[1,10000]之間的數表示。當他使用某種裝備時,他只能使用該裝備的某一個屬性。並且每種裝備最多隻能使用一次。

遊戲進行到最後,lxhgww遇到了終極boss,這個終極boss很奇怪,攻擊他的裝備所使用的屬性值必須從1開始連續遞增地攻擊,才能對boss產生傷害。也就是說一開始的時候,lxhgww只能使用某個屬性值為1的裝備攻擊boss,然後只能使用某個屬性值為2的裝備攻擊boss,然後只能使用某個屬性值為3的裝備攻擊boss……以此類推。現在lxhgww想知道他最多能連續攻擊boss多少次?

輸入描述

輸入的第一行是一個整數N,表示lxhgww擁有N種裝備
接下來N行,是對這N種裝備的描述,每行2個數位,表示第i種裝備的2個屬性值

輸出描述

輸出一行,包括1個數位,表示lxhgww最多能連續攻擊的次數。

範例1

輸入

3
1 2
3 2
4 5

輸出

2

備註

對於 \(30\%\) 的資料,保證 \(N \leq 1000\)
對於 \(100\%\) 的資料,保證 \(N \leq 1000000\)

題解

方法一

知識點:圖論,DFS。

把裝備的兩個屬性值抽象成一條邊的兩個點,每條邊只能選擇一個點,那麼對於一個連通圖有大於等於點數量的邊,那麼這個連通圖是存在環的,那就一定有方法使得所有點都選到,否則最大值不能選到。

於是,建圖後列舉所有點的連通情況,如果存在環就是所有都能取到,如果不存在環則最大值取不到,將第一個不能取到的值設為最大值,如此遍歷所有數位即可得到確定的第一個不能取到的數位,答案就是這個數位減一。

時間複雜度 \(O(n)\)

空間複雜度 \(O(n)\)

方法二

知識點:並查集。

原理和方法一一樣,將存在關係的點放入一個集合,根節點權值設為這個集合的最大值,如果存在兩個點在一個集合後又被合併一次說明這個集合的點存在環,否則沒有。

遍歷所有集合找到不能取到最大值的集合中的最小值,減一即是答案。

時間複雜度 \(O(n)\)

空間複雜度 \(O(1)\)

方法三

知識點:貪心。

對於每個裝備取較小屬性值,如果這個值已經取過了那就取較大的屬性值,將存取資訊存入一個陣列,如此得到一個最小能取到的屬性值的陣列,遍歷陣列直到第一個不能去到的數為止,減一即是答案。

時間複雜度 \(O(n)\)

空間複雜度 \(O(1)\)

程式碼

方法一

#include <bits/stdc++.h>

using namespace std;

vector<int> g[10007];
bool vis[10007];
int maxn;
bool dfs(int u, int fa) {
    bool flag = false;///判斷環
    for (int i = 0;i < g[u].size();i++) {
        int v = g[u][i];
        if (fa == v) continue; ///和其他標記不一樣,父節點單獨考慮
        if (vis[v]) { flag = true; continue; }///有環還不能跳出,要找到最大值
        vis[v] = 1;///標記
        maxn = max(maxn, v);///更新連通塊最大值
        if (dfs(v, u)) flag = true;///傳遞環資訊
    }
    return flag;
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n;
    cin >> n;
    int ans = 0;
    for (int i = 0;i < n;i++) {
        int u, v;
        cin >> u >> v;
        g[u].push_back(v);
        g[v].push_back(u);
        ans = max({ ans,u,v });
    }
    ans++;///表示不能到達的第一個數
    for (int i = 1;i <= ans - 1;i++) {
        if (!vis[i]) {
            vis[i] = 1;
            maxn = i;
            if (!dfs(i, 0)) ans = min(ans, maxn);
        }
        ///如果沒存取,且所在連通塊無環,則僅最大數一定不可達,更新為ans
    ///其他數如果之前的數都可達,則一定可達,因此存取過的不需要再次存取
    }
    cout << ans - 1 << '\n';///遍歷區間後能確定ans
    return 0;
}

方法二

#include <bits/stdc++.h>

using namespace std;

int fa[10007];
int maxn[10007];///維護連通塊最大值
bool flag[10007];///維護環資訊

int find(int x) {
    return fa[x] == x ? x : fa[x] = find(fa[x]);
}

void merge(int u, int v) {
    int ru = find(u);
    int rv = find(v);
    if (ru == rv) flag[ru] = 1;
    else {
        fa[ru] = rv;
        maxn[rv] = max(maxn[ru], maxn[rv]);
        flag[rv] |= flag[ru];
    }
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    for (int i = 1;i <= 10000;i++) fa[i] = i, maxn[i] = i;
    int n;
    cin >> n;
    int ans = 0;
    for (int i = 0;i < n;i++) {
        int u, v;
        cin >> u >> v;
        merge(u, v);
        ans = max({ ans,u,v });
    }
    ans++;
    for (int i = 1;i <= ans - 1;i++) {
        if (fa[i] == i && !flag[i]) ans = min(ans, maxn[i]);
    }
    cout << ans - 1 << '\n';
    return 0;
}

方法三

#include <bits/stdc++.h>

using namespace std;

bool vis[10007];

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n;
    cin >> n;
    for (int i = 0;i < n;i++) {
        int u, v;
        cin >> u >> v;
        if (!vis[min(u, v)]) vis[min(u, v)] = 1;
        else vis[max(u, v)] = 1;
    }
    int ans = 1;
    while (vis[ans]) {
        ans++;
    }
    cout << ans - 1 << '\n';
    return 0;
}