专栏文章

题解:P10156 [LSOT-2] 胜者组

P10156题解参与者 1已保存评论 0

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
0 条
当前快照
1 份
快照标识符
@mio3msyi
此快照首次捕获于
2025/12/02 12:49
3 个月前
此快照最后确认于
2025/12/02 12:49
3 个月前
查看原文
有一道题和本题非常像,可以说是本题的弱化版,建议先去看看:P4158 粉刷匠
对于本题,我们发现可以类比上面那道题的套路,先对于每个小团体内部 dp 求一遍答案,再对所有小团体跑分组背包把答案整合起来。(第三个部分分或许对此有所启发)
现在的问题就是如何算每个小团体内部的贡献,发现很烦人的一点是看起来小团体内部的组合方式是不确定的。再观察算贡献的式子 ai+aj+x×ija_i+a_j+x\times|i-j| 发现 ai+aja_i+a_j 是无论如何都要选的,变数就在于后半部分的绝对值。
如果做题够多,又看到贡献里有下标距离(即下标的差的绝对值),应该第一反应就是借助贪心排序。
考虑假设我们已经选定了某个小团体中要留下的下标集合 SS,那接下来的任务就是给集合中的元素两两配对,形成含有若干二元组的集合 PP,我们要通过合理地顺序安排使得 (i,j)Pxij\sum_{(i,j)\in P} x\cdot|i-j| 最小。
应该不难想到最优策略应该满足:如果把 PP 中每个二元组看成一条线段,则集合内任意线段两两不重叠。因为如果对于两个二元组 (a,b),(c,d)P(a,b),(c,d) \in P,满足 acdba \le c \le d \le b,那么组合成 (a,c),(b,d)(a,c),(b,d) 显然会更优(距离之和显然更小)。
由此,我们可以得出:选择的所有人一定两两相邻,不会出现隔着人跳跃选择的情况,这样就能 dp 了。
先设 fi,j,0/1f_{i,j,0/1} 表示小团体中前 ii 个人中,留下 jj 个学信息学,之前剩余一个未匹配/全部配对时的最小不满度。把贡献拆成 aiix+aj+jxa_i-ix+a_j+jx,不难得出转移:fi,j,0=min{fi1,j1,0,fi1,j,1+ai+x×i}f_{i,j,0}=\min\{f_{i-1,j-1,0}, f_{i-1,j,1}+a_i+x\times i\}fi,j,1=min{fi1,j1,1,fi1,j,0+aix×i}f_{i,j,1}=\min\{f_{i-1,j-1,1}, f_{i-1,j,0}+a_i-x\times i\}。(考虑留/不留第 ii 个人)
然后设 gi,jg_{i,j} 表示前 ii 个小团体留下 jj 个人的最小不满值,用分组背包合并每一行答案即可。
因为所有小团体加起来总共有 nn 个人,所以时间复杂度是 O(n2)O(n^2) 级别的。可以考虑在合并 gg 数组时记录下当前的有效人数,以减少转移开销。
CPP
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 5005;
const ll inf = 1e18;

int n, m, p, x, a[maxn];
vector<int> t[maxn];

void solve()
{
    cin >> n >> m >> p >> x;
    for(int i = 1; i <= n; i ++){
        cin >> a[i];
    }
    for(int c, i = 1; i <= n; i ++){
        cin >> c; t[c].push_back(i);
    }
    vector<vector<ll>> g(n + 1, vector<ll>(m + 1, inf));
    g[0][0] = 0;
    int s = 0; // 记录当前的有效人数
    for(int i = 1; i <= p; i ++){
        vector<vector<array<ll, 2>>> f(t[i].size() + 1, vector<array<ll, 2>>(m + 1, {inf, inf})); // 注意开的大小有说法,开大了TLE
        f[0][0][0] = 0;
        for(int j = 1; j <= t[i].size(); j ++){ // 注意这里j从1开始枚举,但t[i]下标是从0开始的
            for(int k = 0; k <= m; k ++){
                f[j][k][0] = min(f[j-1][k][1]+a[t[i][j-1]]+1LL*x*t[i][j-1], k>0?f[j-1][k-1][0]:inf); // 避免k-1越界
                f[j][k][1] = min(f[j-1][k][0]+a[t[i][j-1]]-1LL*x*t[i][j-1], k>0?f[j-1][k-1][1]:inf);
            }
        }
        s += t[i].size();
        for(int j = 0; j <= s; j ++){
            for(int k = 0; j + k <= m && k <= t[i].size(); k ++){ // 注意j+k不要越界
                g[i][j+k] = min(g[i][j+k], g[i-1][j]+f[t[i].size()][k][0]);
            }
        }
    }
    ll ans = inf;
    for(int j = 0; j <= m; j ++){ // 枚举所有可能留下的人数
        ans = min(ans, g[p][j]);
    }
    if(ans >= 1e15) cout << "Impossible\n";
    else cout << ans << '\n';
}

signed main()
{
    ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
    solve();
    return 0;
}

评论

0 条评论,欢迎与作者交流。

正在加载评论...