[Educational Codeforces Round 87 (div2)]1354F - Summoning Minions[dp][费用流]



参考博客 d p dp dp做法

费用流做法

1354F - Summoning Minions[ d p dp dp][费用流]

题意

可以召唤 n n n 个奴才,战场上最多同时可以有 k k k 个奴才
每个奴才权值为 a [ i ] a[i] a[i],召唤到场上后原先场上的奴才权值都可以加上 b [ i ] b[i] b[i]
问要使得最后在场上的权值最大
可以按什么样的顺序放奴才,最大的权值为多少

做法

要使得最后权值尽可能大,可以在场上有 k − 1 k-1 k1 个奴才的时候,把最后不要再场上的奴才都放场上,这样可以最大化 b [ i ] b[i] b[i] 的价值
最终留在场上的必然是前 k k k 大的 a [ i ] a[i] a[i],问题在于如何放这 k k k 个可以最大化权值
首先将奴才进行排序,将 a [ i ] a[i] a[i] 大的排在前面,这样可以保证取得的前 k k k 个都是 a [ i ] a[i] a[i] k k k 大的
求最大值可以考虑用 d p dp dp

d p [ i ] [ j ] dp[i][j] dp[i][j] 表示到第 i i i 个奴才为止已经选择了 j j j 个奴才上场所可以得到的最大值
利用 v i s [ i ] [ j ] vis[i][j] vis[i][j] 标记 d p [ i ] [ j ] dp[i][j] dp[i][j] 可以选的
转移有两种情况
一种是这个是最后要抛弃的那些奴才
另一种是最后要放在场上的 k k k
再逆序找到满足的选择方案

还有另一种做法是费用流
具体可以看我给的链接,毕竟自己还没过
自己写的有点问题(怕不是板子有问题),挠秃头,这两天有空再试一下

输入

3
5 2
5 3
7 0
5 0
4 0
10 0
2 1
10 100
50 10
5 5
1 5
2 4
3 3
4 2
5 1

输出

4
2 1 -1 5
1
2
5
5 4 3 2 1

Code: dp

#include <bits/stdc++.h>
#define max(a,b)    ((a)>(b)?(a):(b))
#define min(a,b)    ((a)<(b)?(a):(b))
using namespace std;
typedef long long ll;
const int maxn = 100 + 5;

struct node{
    ll x, y; int id;
    bool operator < (const node &ope) const {
        return y < ope.y;
    }
}a[maxn];
bool vis[maxn][maxn];
ll dp[maxn][maxn];
bool vis2[maxn];

inline void init() {
    memset(dp, -1, sizeof(dp));
    memset(vis, false, sizeof(vis));
    memset(vis2, false, sizeof(vis2));
}

void solve(int n, int k) {
    init();
    dp[0][0] = 0;
    sort(a + 1, a + 1 + n);
    for(int i = 1; i <= n; ++i) {
        int t = min(i, k);
        for(int j = 0; j <= t; ++j) {
            if(dp[i - 1][j] >= 0)  // 这个作为最后要丢掉的
                dp[i][j] = dp[i - 1][j] + (k - 1) * 1ll * a[i].y;
            if(j > 0 && dp[i - 1][j - 1] >= 0) {
                if(dp[i][j] >= dp[i - 1][j - 1] + a[i].x + (j - 1) * 1ll * a[i].y)
                    continue;
                // 在第 i 个位置选 j 可以得到更大值
                dp[i][j] = dp[i - 1][j - 1] + a[i].x + (j - 1) * 1ll * a[i].y;
                vis[i][j] = true;
            }
        }
    }
}

void print(int n, int k) {
    printf("%d\n", k + (n - k) * 2);
    for(int i = n, j = k; i > 0 && j >= 0; --i) {
        if(vis[i][j])
            vis2[i] = true, --j;
    }
    int t = 0;
    for(int i = 1; i <= n; ++i) {
        if(!vis2[i])  continue;
        if(++t == k) {
            t = a[i].id;
            break;
        }
        printf("%d ", a[i].id);
    }
    for(int i = 1; i <= n; ++i) {
        if(!vis2[i])
            printf("%d %d ", a[i].id, -a[i].id);
    }
    printf("%d\n", t);
}

int main() {
    int T;
    scanf("%d", &T);
    while(T--) {
        int n, k;
        scanf("%d%d", &n, &k);
        for(int i = 1; i <= n; ++i) {
            scanf("%lld%lld", &a[i].x, &a[i].y); a[i].id = i;
        }
        solve(n, k);
        print(n, k);
    }
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值