AcWing第129场周赛 - 5289. 奶牛做题 - 思维/贪心

可以把数据想象成n行k列的矩阵,这个矩阵每一列上的值相等。
每完成矩阵上的一张试卷会花费a[i]的时间,同时获得分数+1。另外完成一整行的试卷,会额外+1。
给定我们一个总时间M,问如果使得获得的分数最大。

正解:枚举已经完成了0行,1行,2行……(每完成一行会额外+1)
然后用剩下的时间,在剩下的行中按照列去取,直至不能取为止。

#include 
#include 
#include 
#include 
#include 
using namespace std;
const int N = 2e5 + 10;
#define de(x) cout << x << " ";
#define sf(x) scanf("%d", &x);
#define Pu puts("");
#define ll long long
int n, m, ans;
int a[N];
int k;
int main() {
    cin >> n >> k >> m;
    int sz = 0;
    for (int i = 1; i <= k; i++) {
        cin >> a[i];
        sz += a[i];
    }
    sort(a + 1, a + k + 1);
    int res;
    int tmp_sz;
    ans = 0;
    for (int i = 0; i <= n; i++) {
        tmp_sz = m - i * sz;  // 此时拿掉i行后还剩下多少时间
        if (tmp_sz < 0)
            break;
        res = i * (k + 1);  // 拿掉i行后获得的分数

        bool flag = true;
        for (int j = 1; j <= k; j++) {
            for (int o = i + 1; o <= n; o++) {
                if (tmp_sz >= a[j]) {
                    tmp_sz -= a[j];
                    res++;
                } else {
                    flag = false;
                    break;
                }
            }
            if (flag == false)
                break;
        }
        ans = max(ans, res);
    }
    de(ans);
    return 0;
}

错误思路

其实上面的思路是看了题解后写的,之前一直想的是。
一直先按照列拿,额外的1处理主要是在倒数第二列或者倒数第一列,要么按照行拿,要么按照列拿。但是会有一个问题,就是我们可能会舍弃能拿的某两列,而去拿某两行,目的是获得额外的分数。
通过样例:14/15

#include 
#include 
#include 
#include 
#include 
using namespace std;
const int N = 2e5 + 10;
#define de(x) cout << x << " ";
#define sf(x) scanf("%d", &x);
#define Pu puts("");
#define ll long long
int n, m, ans;
int a[N];
int k;
int fun() {  // 要么竖着拿,要么横着拿
    int l = 0;
    int tmp_m = m;
    while (tmp_m) {  // 竖着拿
        // 先拿倒数第二列
        if (k > 1) {  // 防止没有倒数第二列
            for (int j = 1; j <= n; j++) {
                if (tmp_m >= a[k - 1]) {
                    tmp_m -= a[k - 1];
                    l++;
                } else {
                    tmp_m = 0;
                    break;
                }
            }
            if (tmp_m == 0)
                break;
        }
        // 再拿倒数第一列
        for (int j = 1; j <= n; j++) {
            if (tmp_m >= a[k]) {
                tmp_m -= a[k];
                l += 2;  // 倒数第一列的值额外加一
            } else {
                tmp_m = 0;
                break;
            }
        }
        break;  // 防止m很大,全部拿完
    }
    int r = 0;
    tmp_m = m;
    while (tmp_m) {  // 横着拿
        for (int j = 1; j <= n; j++) {
            if (k > 1) {                  // 防止没有倒数第二列
                if (tmp_m >= a[k - 1]) {  // 先拿倒数二列
                    tmp_m -= a[k - 1];
                    r++;
                } else {
                    tmp_m = 0;
                    break;
                }
            }
            if (tmp_m >= a[k]) {  // 再拿倒数第一列
                tmp_m -= a[k];
                r += 2;
            } else {
                tmp_m = 0;
                break;
            }
        }
        break;  // 防止m很大,全部拿完
    }
    // de("666") de(l) de(r) Pu;
    return max(l, r);
}
int main() {
    cin >> n >> k >> m;
    for (int i = 1; i <= k; i++) {
        cin >> a[i];
    }
    sort(a + 1, a + k + 1);  // 注意从小到大排序
    ans = 0;
    // 看成n行k列的矩阵
    for (int i = 1; i <= k; i++) {
        if (m > a[i] * n && i < k - 1) {  // 注意这里是>而不是>=
            // 因为如果是恰好倒数第二列拿完,那么有可能拿倒数第一列的数得到的结果更大
            ans += n;
            m -= a[i] * n;
        } else {
            // 到这里还有一个样例过不了。
            // 其实后来发现,当存在某一行拿不下时,有可能我们会舍弃某两列
            // 而是去拿两行,为了获得额外的数量
            // 题解里面是直接拿0,1,2行,剩下的再去拿列
            if (i < k - 1) {
                int tmp = 0;
                for (int j = i; j <= k; j++) {
                    tmp += a[j];
                }
                de("666") de(ans) de(i) de(m) de(a[i]) de(m / a[i]) Pu;
                de(tmp) de(k - i + 1 + (m - tmp) / a[i] + 1) Pu;
                ans += m / a[i];  // 向下取整
                break;
            } else {
                // 如果是倒数第二列或者倒数第一列
                ans = ans + fun();
                break;  // 注意直接退出循环,因为我们已经处理了倒数第二列和倒数第一列
            }
        }
    }
    de(ans);
    return 0;
}

当时随便看了一眼题解后,写的另外一份代码。
注意是枚举先拿0-n行,之后再按照列进行拿,而不是上来就一口气把行拿到不能拿为止。
错误代码2:

#include 
#include 
#include 
#include 
#include 
using namespace std;
const int N = 2e5 + 10;
#define de(x) cout << x << " ";
#define sf(x) scanf("%d", &x);
#define Pu puts("");
#define ll long long
int n, m, ans;
int a[N];
int k;
int main() {
    cin >> n >> k >> m;
    int sz = 0;
    for (int i = 1; i <= k; i++) {
        cin >> a[i];
        sz += a[i];
    }
    sort(a + 1, a + n + 1);
    // 上面我调试了那么就,看题解发现其实就是
    // 1、整套试卷做
    // 2、挑花费时间最小的做
    // 上面两种情况下去得分最高即可,我还是给想复杂了(orz)
    int l = 0;  // 做整套试卷
    int tmp_m = m;
    l = (tmp_m / sz) * (k + 1);  // 并不是一口气拿完能拿的行
    tmp_m = tmp_m - sz * l / (k + 1);
    // de(tmp_m);
    int f = 1;
    for (int i = 1; i <= k; i++) {          // 对每一列
        for (int j = k - l; j <= k; j++) {  // 看剩下的行
            if (tmp_m > a[i]) {
                l++;
                tmp_m -= a[i];
            } else {
                f = 0;
                break;
            }
        }
        if (!f) {
            de("666") de(l) de(i) Pu;
            break;
        }
    }

    int r = 0;  // 先花费时间挑小的做
    for (int i = 1; i <= k; i++) {
        if (m >= n * a[i]) {
            r += n;
            m -= n * a[i];
            if (i == k)
                r += n;
        } else {
            while (m) {
                if (m >= a[i]) {
                    m -= a[i];
                    r++;
                    if (i == k)
                        r++;
                } else {
                    break;
                }
            }
        }
    }
    de(max(l, r));
    return 0;
}

你可能感兴趣的:(#,算法/思维,基础刷题,算法,c++,数据结构)