题解:P10156 [LSOT-2] 胜者组
有一道题和本题非常像,可以说是本题的弱化版,建议先去看看:P4158 粉刷匠。
对于本题,我们发现可以类比上面那道题的套路,先对于每个小团体内部 dp 求一遍答案,再对所有小团体跑分组背包把答案整合起来。(第三个部分分或许对此有所启发)
现在的问题就是如何算每个小团体内部的贡献,发现很烦人的一点是看起来小团体内部的组合方式是不确定的。再观察算贡献的式子
如果做题够多,又看到贡献里有下标距离(即下标的差的绝对值),应该第一反应就是借助贪心排序。
考虑假设我们已经选定了某个小团体中要留下的下标集合
应该不难想到最优策略应该满足:如果把
由此,我们可以得出:选择的所有人一定两两相邻,不会出现隔着人跳跃选择的情况,这样就能 dp 了。
先设
然后设
因为所有小团体加起来总共有
#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;
}