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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值