21 S2模拟赛T3 网球比赛 题解

网球比赛

题面

网球队有 \(m\) 个队员,现在要选出 \(2n\) 个队员两两配对去打 \(n\) 场网球比赛。

\(i\) 个队员有能力值为 \(e_i\) ,根据训练时长分为初级和高级两类,如果 \(t_i = 1\) ,为初级,否则为高级。

每场比赛有 \(l_i\) ,表示本场比赛的选手能力值不能超过 \(l_i\)

为了稳定性考虑,两名参赛选手 \(|e_i - e_j| \le d\)

教练想知道,对于 \(k = 0, 1, \cdots, 2n\) ,是否存在恰好选出 \(k\) 名初级队员,又满足上述约束的方案。若不存在,输出 -1,否则输出最大能力值。

\(1 \le n \le 20, 2n \le m \le 10^5\)

\(1 \le e_i, l_i \le 10^9, 0 \le d \le 10^9\)

题解

这道题赛时看到,感觉很麻烦,约束太多,但其实应该尝试想一想的,因为这道题并不是那么难。

因为要能力值的差尽可能小,并且比赛对能力值有限制,所以我们可以先将队员和比赛都先排序。

对于一个队员 \(i\) ,我们选其作为一组中能力值较大的一个,那么我们要在前面选另一个队员 \(x\) 使得 \(e_i + e_x\) 尽可能大,那么 \(x\) 一定是越大越好,唯一的区别就是 \(x\) 是否是初级队员。

所以我们可以对于每个位置 \(i\) 都记录一个 \(pre[i][0/1]\) 表示 \(i\) 前面的第一个初级/高级队员的位置,这样我们就能快速进行转移了。

然后进行 dp,设 \(f(i,j,k)\) 表示配好了前 \(i\) 场比赛的队伍,用了前 \(j\) 个人中的若干个,选出来的人里有 \(k\) 个初级的最大能力值。

有转移

\[\begin{align*} f(i,j,k) = \begin{cases} f(i, j - 1, k) \quad \text{不选第 j 个人} \\ f(i - 1, pre[j][0] - 1, k - [t_j = 1] - 1) + e_j + e_{pre[j][0]} \\ f(i - 1, pre[j][1] - 1, k - [t_j = 1]) + e_j + e_{pre[j][1]} \\ \end{cases} \end{align*} \]

如果直接搞,空间会炸,所以再加个滚动数组优化即可过掉这题。

时间复杂度 \(O(n^2m)\) ,空间复杂度 \(O(nm)\)

code

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>

using namespace std;

namespace michaele {

    typedef long long ll;
    const int N = 25, M = 1e5 + 10;

    int n, m, D;
    pair <int, int> a[M];
    int pre[M][2], l[N];
    ll f[2][M][N << 1];

    void solve () {
        cin >> n >> m >> D;
        for (int i = 1; i <= n; i ++) {
            cin >> l[i];
        }
        for (int i = 1; i <= m; i ++) {
            cin >> a[i].first >> a[i].second;
        }
        sort (l + 1, l + 1 + n);
        sort (a + 1, a + 1 + m);

        int p0 = 0, p1 = 0;
        for (int i = 1; i <= m; i ++) {
            pre[i][0] = p0;
            pre[i][1] = p1;
            if (a[i].second == 1) p0 = i;
            else p1 = i;
        }

        memset (f, 0xaf, sizeof f);
        f[0][0][0] = 0;

        for (int i = 0; i <= n; i ++) {
            int bit = i & 1;
            for (int j = 1; j <= m; j ++) {
                for (int k = 0; k <= n * 2; k ++) {
                    auto &now = f[bit][j][k];

                    // 不选第 j 个
                    now = max (now, f[bit][j - 1][k]);

                    // 选第 j 个
                    if (i && a[j].first <= l[i]) {
                        if (pre[j][0] > 0 && a[j].first - a[pre[j][0]].first <= D) {
                            int val = a[j].first + a[pre[j][0]].first;
                            int junior = (a[j].second == 1) + 1;
                            if (k >= junior) {
                                now = max (now, f[bit ^ 1][pre[j][0] - 1][k - junior] + val);
                            }
                        }
                        if (pre[j][1] > 0 && a[j].first - a[pre[j][1]].first <= D) {
                            int val = a[j].first + a[pre[j][1]].first;
                            int junior = (a[j].second == 1);
                            if (k >= junior) {
                                now = max (now, f[bit ^ 1][pre[j][1] - 1][k - junior] + val);
                            }
                        }
                    }
                }
            }
            memset (f[bit ^ 1], 0xaf, sizeof f[bit ^ 1]);
        }

        for (int i = 0; i <= n * 2; i ++) {
            if (f[n & 1][m][i] < 0) cout << -1 << ' ';
            else cout << f[n & 1][m][i] << ' ';
        }
        cout << endl;
    }

    void Main () {
        ios :: sync_with_stdio (0);
        cin.tie (0);
        cout.tie (0);

        int T;
        cin >> T;
        while (T --) {
            solve ();
        }
    }
}

int main () {

    michaele :: Main ();

    return 0;
}
posted @ 2025-10-05 17:56  michaele  阅读(3)  评论(0)    收藏  举报