Olympiad in Programming and Sports
题目链接
https://codeforces.com/contest/730/problem/I
题目大意
有 N 个人,每个人编程能力为 xi ,运动能力为 yi
现要求你选 p 个人参加编程比赛 , s 个人参加运动比赛(每个人只能参加一项比赛)
使得这 p 个人编程能力和 + 这 s 个人运动能力和最大,问怎么选择
解题思路
dp
我们可以先以一种能力降序排序(假设以 yi)
那么排完序后,因为是降序的,所以参加运动的人选择的范围肯定是 [1 , p + s]
而参加编程队的我们就无法确定
依此我们可以建立dp方程 dp[i][j] 表示前 i 个人有 j 个选择参加编程比赛
那么转移方程也很显然:
dp[i][j] = max(①dp[i - 1][j] , ②dp[i - 1][j - 1] + a[i].x , ③dp[i - 1][j] + a[i].y)
答案为dp[n][p]
其中当 i - j > s 时不再进行③的转移,因为此时 [1 , i - i] 已经有大于等于S个人没参加编程比赛 , 也就是这些人是可以参加运动比赛的,而数组是以运动能力降序排序所以前面的人参加运动比赛的收益一定比[i , n]的人高,所以参加运动比赛的人肯定不会从后面选择
这也是能保证 dp[n][p] 的选择方案里一定有 S 个参加运动比赛的原因(因为是降序的所以当运动人数没满时不参加编程比赛的一定去了运动比赛而不是哪都没去)
AC_Coder
#include<bits/stdc++.h> #define rep(i,a,n) for (int i=a;i<=n;i++) #define int long long using namespace std; const int N = 3e3 + 10; struct node{ int x , y , id; bool operator < (node const & a) const { return y > a.y; } }a[N]; int dp[N][N] , vis[N]; pair<int , int>pre[N][N]; void dfs(int i , int j) { if(i == 0) return ; if(pre[i][j].second == 1) vis[i] = 1 ; dfs(i - 1 , pre[i][j].first) ; } signed main() { int n , p , s; cin >> n >> p >> s; rep(i , 1 , n) cin >> a[i].x , a[i].id = i; rep(i , 1 , n) cin >> a[i].y; sort(a + 1 , a + 1 + n); memset(dp , -0x3f3f3f , sizeof(dp)); dp[0][0] = 0; rep(i , 1 , n) { rep(j , 0 , min(i , p)) { if(i - j <= s && dp[i - 1][j] + a[i].y > dp[i][j]) dp[i][j] = dp[i - 1][j] + a[i].y , pre[i][j] = make_pair(j , 2); if(i - j > s && dp[i - 1][j] > dp[i][j]) dp[i][j] = dp[i - 1][j] , pre[i][j] = make_pair(j , 0); if(j - 1 >= 0 && dp[i - 1][j - 1] + a[i].x > dp[i][j]) dp[i][j] = dp[i - 1][j - 1] + a[i].x , pre[i][j] = make_pair(j - 1 , 1); } } cout << dp[n][p] << '\n'; dfs(n , p) ; int cnt = 0 ; rep(i , 1 , n) if(vis[i]) cout << a[i].id << " "; cout << '\n'; rep(i , 1 , n) if(!vis[i] && cnt < s) cout << a[i].id << " " , cnt ++ ; cout << '\n'; return 0; }
凡所不能将我击倒的,都将使我更加强大