#BestCoder Round #59 (div.2)

@(K ACMer)


A. SDOI

此题不说,结构体排序.


B. Reorder the Books

题意:
给你一排包含1到n的n (n<=19) 个数字,每次只能把序列中的任意一个数字抽出来放到序列的左边,问要把这个序列变换到递增序列需要对最少步数是多少?
分析
- 首先看到n的数据范围竟然才19,想到的就是可能会用到状态压缩DP.然后来观察具有哪些性质:

1.对于每个数做一次交换只能把它的位置向左移动,所以最大的数没有必要做交换,而它右边的数必须做交换.
2.对于一个数,最多做一次交换,因为它做多次交换都可以转换成一次来完成.
3.大的数总是比小的数先交换,因为如果小的数交换到了大的数右边,那么小的数还需要再交换这是无意义的.因此我们可以从n到1,按顺序把数放到数列左边,这样将得到一个可行的方案,但是它并不一定是最优的.(这里我们将得到一个一个非常暴力的方法,就是枚举哪些数被交换了,哪些数没有被交换,共有 2n 中情况,每种情况下,肯定是大的数先交换,所以把大的数排个序加在不交换的数后面然后来看是否形成顺序序列,这样总的复杂度是 O(2nnlogn) ,这里才发现原来给19的数据范围不是要写状态压缩DP,而是一种复杂度和状态压缩DP相似的枚举算法=_+)
4.最大的数不用交换,反正比它小的数,都会交换到它左边,最后它自然是最右边了.既然这样….那么,如果第二大的数在最大的数左边它也不需要交换,因为它和最大的数中间的数,最终会被交换到它左边……那么第三大的数如果在最大的数左边也不用……..(好这里根据这个性质就想出了,这个题的最优化做法,找出序列中的以n结尾的,梯度为1的递增子序列的长度x,这x个数都是不需要变的,所以只需要交换n -x次,而这个算法的复杂度是 O(n) !,很优美有没有?!)


code:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <set>
#include <map>
#include <stack>
#include <vector>
#include <string>
#include <queue>
#include <cstdlib>
#include <cmath>
#include <algorithm>
using namespace std;
const int mod = int(1e9) + 7, INF = 0x3fffffff, M = int(1e5) + 17;
int n, m, a[1000];


int main(void) {
    int T;
    cin >> T;
    for (int cas = 1; cas <= T; cas++) {
        cin >> n;
        for(int i = 0; i < n; i++) {
            cin >> a[i];
        }
        int cnt = 0, l = n;
        for(int i = n - 1; i >= 0; i--) {
            if(a[i] == l) {
                cnt++;
                l--;
            }
        }
        printf("%d\n", n - cnt);
    }
    return 0;
}

C.The Highest Mark

题意:
一场进行t分钟的比赛有n道题,每道题你都可以做,且这道题的初始分值为a,每分钟减少的分值为b,你做完这道题需要的时间是c.问你安排最优策略来做这道题,你能获得的最大利润是多少?
分析:
- 显然如果没有分数随着时间减少这个特点,这是一个01背包问题,01背包问题选择物品是无关顺序的.但是这个题有随着时间减少分数会减少的特点.
- 也就是选择物品的顺序会对结果产生影响了.如果枚举所有可能的顺序显然将有 n! 的情况,复杂度太高=_=.那么有没有一个最优的顺序呢?
- 在讨论整个序列的最优顺序,我们可以把问题分解为,是否两个相邻的数a ,b,有一个最优策略来决定a在b前面还是b在a前面?

我们假设a在b前面,那么将要损失的分数是: b.ba.c
假设b在a前面,那么将要损失的分数是 a.bb.c
我们需要损失小的反正序列前面,就是 b.ba.c>a.bb.c 也就是 a.c/a.b>b.c/b.b 按照这个规则排序就可以得到,选择物品的顺序了.

得到顺序之后,我们只需要01背包的选择物品就可以了!
code

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
struct dot{
    int a, b, c;
}v[1009];

bool cmp(const dot& a, const dot& b) {
    return 1. * a.b / a.c > 1. * b.b / b.c;
}

int main(void) {
    int T;
    cin >> T;
    for (int cas = 1; cas <= T; cas++) {
        int n, t, dp[3009];
        memset(dp, 0, sizeof(dp));
        scanf("%d%d", &n, &t);
        for (int i = 0; i < n; i++) scanf("%d%d%d", &v[i].a, &v[i].b, &v[i].c);
        sort(v, v + n, cmp);
        for (int i = 0; i < n; i++) {
            for (int j = t; j >= v[i].c; j--) {
                dp[j] = max(dp[j], dp[j - v[i].c] + v[i].a - v[i].b * j);
            }
        }
        int ans = 0;
        for (int i = 0; i <= t; i++) ans = max(ans, dp[i]);
        printf("%d\n", ans);
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值