有多少玩家无法赢得游戏?

3

我们有n个玩家,每个玩家都有三个值 A,B 和 C。

如果存在另一个玩家 j 满足 A[j] > A[i],B[j] > B[i],C[j] > C[i],那么玩家 i 就无法获胜。我们要找出无法获胜的玩家数量。

我尝试使用暴力搜索解决此问题,即对玩家数组进行线性搜索,但运行时间过长 (TLE)。

对于每个玩家i,我遍历整个数组以查找是否存在任何其他玩家j,其满足上述条件。

代码:

int count_players_cannot_win(vector<vector<int>> values) {
    int c = 0;
    int n = values.size();
    for(int i = 0; i < n; i++) {
        for(int j = 0; j!= i && j < n; j++) {
            if(values[i][0] < values[j][0] && values[i][1] < values[j][1] && values[i][2] < values[j][2]) { 
                c += 1;
                break;
           }
        }    
    }
    return c;
}

这种方法的时间复杂度是 O(n^2),因为对于每个选手,我们都要遍历整个数组。因此,它会导致 TLE。

样例测试用例

Sample Input

3(number of players)
A B C
1 4 2
4 3 2
2 5 3

Sample Output :

1 

Explanation :
Only player1 cannot win as there exists player3 whose all 3 values(A, B and C) are greater than that of player1.

约束条件:

n(number of players) <= 10^5

如何最优地解决这个问题?

解决方案:

int n;
const int N = 4e5 + 1;
int tree[N];

int get_max(int i, int l, int r, int L) {  // range query of max in range v[B+1: n]
    if(r < L || n <= l)
        return numeric_limits<int>::min();
    else if(L <= l)
        return tree[i];
    int m = (l + r)/2;
    return max(get_max(2*i+1, l, m, L), get_max(2*i+2, m+1, r, L));
}
void update(int i, int l, int r, int on, int v) { // point update in tree[on]
    if(r < on || on < l) 
        return;
    else if(l == r) {
        tree[i] = max(tree[i], v);
        return;
    }
    int m = (l + r)/2;
    update(2*i+1, l, m, on, v);
    update(2*i+2, m + 1, r, on, v);
    tree[i] = max(tree[2*i+1], tree[2*i+2]);
}

bool comp(vector<int> a, vector<int> b) {
    return a[0] != b[0] ? a[0] > b[0] : a[1] < b[1];  
}

int solve(vector<vector<int>> &v) {
    n = v.size();
    vector<int> b(n, 0);           // reduce the scale of range from [0,10^9] to [0,10^5]
    for(int i = 0; i < n; i++) {
        b[i] = v[i][1];
    }
    for(int i = 0; i < n; i++) {
        cin >> v[i][2];
    }

//     sort on 0th col in reverse order
    sort(v.begin(), v.end(), comp);
    sort(b.begin(), b.end());
    
    int ans = 0;
    for(int i = 0; i < n;) {
        int j = i;
        while(j < n &&  v[j][0] == v[i][0]) {    
            int B = v[j][1];
            int pos = lower_bound(b.begin(), b.end(), B) - b.begin(); // position of B in b[]
            int mx = get_max(0, 0, n - 1, pos + 1);
            if(mx > v[j][2])
                ans += 1;
            j++;
        }
        while(i < j) {
            int B = v[i][1];
            int C = v[i][2];
            int pos = lower_bound(b.begin(), b.end(), B) - b.begin(); // position of B in b[]
            update(0, 0, n - 1, pos, C);
            i++;
        }
    }
    return ans;
}

该解决方案使用线段树(segment tree),因此以时间复杂度O(n*log(n))和空间复杂度O(n)的方式解决了问题。

方法在接受的答案中由@Primusa解释。


能够获胜的玩家集合被称为“帕累托集”(Pareto set),也称为“帕累托前沿”或“帕累托边界”。搜索这些术语将找到其他算法讨论,例如https://stackoverflow.com/questions/43075516/how-do-i-find-the-pareto-optimal-points-in-onh-and-onlogh-complexity。 - Geoffrey Brent
1个回答

3
首先假设我们的输入形式是一个元组列表:T = [(A[0], B[0], C[0]), (A[1], B[1], C[1]) ... (A[N - 1], B[N - 1], C[N - 1])]
第一项观察是,我们可以按照T[0](逆序)进行排序。然后对于每个元组 (a,b,c) ,要确定它是否不能胜利,我们会问自己是否已经看到了这样一个元组 (d,e,f),使得e > b && f > c。我们不需要检查第一个元素,因为由于T逆序排序,我们知道给定的d > a
好的,那么我们如何检查这个第二个条件呢?
我们可以这样重新表述:在我们已经看到的所有元组(d,e,f)中,具有e> b的最大值是多少?如果最大值大于c,则我们知道该元组不能获胜。
为了处理这部分内容,我们可以使用带有最大更新和最大范围查询的线段树。当我们遇到元组(d,e,f)时,我们可以设置tree[e] = max(tree [e],f)tree [i] 将表示第三个元素,其中i是第二个元素。
要回答“在e > b的情况下f的最大值是多少”的查询,我们执行max(tree[b+1...]),以获得可能的第二个元素范围内最大的第三个元素。
因为我们只做后缀查询,所以可以使用修改后的Fenwick树,但使用线段树更容易解释。
这将为我们提供一个O(NlogN)的解决方案,用于对T进行排序,并且对于每个元组在我们的线段树中需要O(logN)的处理时间。
*注意:实际上应该是d >= a。然而,假装一切都是唯一的时候更容易解释算法。您需要做出的唯一修改是根据第一个元素的相同值处理您的查询和更新元组的存储桶。这意味着我们将检查所有具有相同第一个元素的元组,然后仅针对我们在这些元组上执行检查的所有元组进行tree[e] = max(tree[e], f)的更新。这确保了当另一个元组查询该树时,具有相同第一个值的任何元组已经更新了树。

谢谢。我将使用线段树方法更新问题并提供解决方案代码。 - tusharRawat
这是一个非常棒的解决方案。我一直在考虑使用线段树方法,但你在这里轻松地阐述它令人印象深刻!你能推荐一些学习和练习线段树问题的好资源吗? - Serial Lazer
1
@SerialLazer 当然可以:https://cp-algorithms.com/data_structures/segment_tree.html 是一个非常高质量的资源,底部还有大量的练习问题。此外,https://codeforces.com/blog/entry/18051 也是一个很好的资源,可以在完成cp-algorithms文章后实现它们的一种非常好的方式。 - Primusa

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接