[CSP-J 2019]T3纪念品 题解 by 对面龙哥

简要分析题目

  • 题目链接(AcWing)
  • 题目链接(洛谷)

刚看到题时我觉得很简单,每天都买涨幅最大的就好了,就是道很简单的贪心嘛,然后看了一下别人的题解,用的全是DP,一看就是逊啦!

[CSP-J 2019]T3纪念品 题解 by 对面龙哥_第1张图片[CSP-J 2019]T3纪念品 题解 by 对面龙哥_第2张图片[CSP-J 2019]T3纪念品 题解 by 对面龙哥_第3张图片
然后我就写了个超勇的贪心,结果……

贪心思路

处理每天的价格

由于每行输入代表一天的物价,所以可以把问题分到每一天,每输入一天物价处理一次。
首先用了一个类表示每天的价格和第二天的上涨量(也可能是下跌):


class ITEM {//将涨价单独列出方便排序
public:
    int id, value;
    ITEM() :id(0), value(0) {}
    inline void update(int i, int v) { id = i; value = v; return; }
};
inline bool cmp(ITEM, ITEM);//声明cmp函数
class DAY {
    int* price;//物品种类数不确定,用动态数组避免空间浪费
    ITEM* rise;
public:
    friend class WEI;//后面将小伟抽象为一个类,设为友元类方便WEI中成员函数访问
    friend inline bool cmp(ITEM,ITEM);//将cmp函数设为友元函数
    DAY() {}
    ~DAY() {//析构函数,销毁对象时将对象申请的内存释放
        delete[]price;释放price指向的内存
        delete[]rise;释放rise指向的内存
    }
    inline void set() {初始化(第一天时没有前一天的价格来衡量涨价)
        price = new int[n];
        for (int i = 0; i < n; i++)price[i] = read();
        rise = new ITEM[n];
    }
    void update() {//每天更新价格
        int x;
        for (int i = 0; i < n; i++)price[rise[i].id] += rise[i].value;//根据昨天物价和涨价计算今天物价
        for (int i = 0; i < n; i++) {
            x = read();
            rise[i].update(i, x - price[i]);//根据明天物价和今天物价计算涨价
        }
        sort(rise, rise + n, cmp);根据涨幅排序
    }
}day_data;

于是就可以利用 update函数每天更新。

处理每天 -阿玮- 小伟的买卖行为

我们写出上文提到的小伟类,将买和卖统一,每次购买后就换算为第二天金钱,省去处理物品数量.

class WEI {
    int money;//小伟金钱
public:
    WEI() :money(0) {}
    inline void give_money(int x = read()) { money = x; return; }//初始化小伟金钱
    void update(const DAY& d = day_data) {//更新小伟的买卖行为后的金钱
        int temp = 0;
        for (int i = 0; day_data.rise[i].value > 0 && i < n && money; i++) {
            temp += (money / d.price[d.rise[i].id]) * (d.rise[i].value +
                d.price[d.rise[i].id]);//每次买完物品立即换算成第二天的价格存入temp
            money %= d.price[d.rise[i].id];//以今天价格扣除金钱
        }
        money += temp; return;剩余金钱和交易所得金钱相加得到最终金钱
    }
    inline void print() { printf("%d", money); return; }//输出小伟金钱
}_wei;

模拟

根据题意直接模拟即可:

int main() {
    t = read() - 1;
    n = read();
    _wei.give_money();
    day_data.set();
    for (int i = 0; i < t; i++) {
        day_data.update();
        _wei.update();
    }
    _wei.print();
    return 0;
}

贪心思路完整代码

代码如下:

#include
#include
#include
using namespace std;
int t, n;
inline int read() {
    int x = 0;
    char ch = getchar();
    while (ch > '9' || ch < '0')ch = getchar();
    while (ch >= '0' && ch <= '9') {
        x = (x << 1) + (x << 3) + (ch - 48);
        ch = getchar();
    }
    return x;
}
class ITEM {
public:
    int id, value;
    ITEM() :id(0), value(0) {}
    inline void update(int i, int v) { id = i; value = v; return; }
};
inline bool cmp(ITEM, ITEM);
class DAY {
    int* price;
    ITEM* rise;
public:
    friend class WEI;
    friend inline bool cmp(ITEM,ITEM);
    DAY() {}
    ~DAY() {
        delete[]price;
        delete[]rise;
    }
    inline void set() {
        price = new int[n];
        for (int i = 0; i < n; i++)price[i] = read();
        rise = new ITEM[n];
    }
    void update() {
        int x;
        for (int i = 0; i < n; i++)price[rise[i].id] += rise[i].value;
        for (int i = 0; i < n; i++) {
            x = read();
            rise[i].update(i, x - price[i]);
        }
        sort(rise, rise + n, cmp);
    }
}day_data;
inline bool cmp(ITEM a, ITEM b) {
    return a.value ^ b.value ? a.value > b.value:day_data.price[a.id] 
        < day_data.price[b.id];
}
class WEI {
    int money;
public:
    WEI() :money(0) {}
    inline void give_money(int x = read()) { money = x; return; }
    void update(const DAY& d = day_data) {
        int temp = 0;
        for (int i = 0; day_data.rise[i].value > 0 && i < n && money; i++) {
            temp += (money / d.price[d.rise[i].id]) * (d.rise[i].value +
                d.price[d.rise[i].id]);
            money %= d.price[d.rise[i].id];
        }
        money += temp; return;
    }
    inline void print() { printf("%d", money); return; }
}_wei;
int main() {
    t = read() - 1;
    n = read();
    _wei.give_money();
    day_data.set();
    for (int i = 0; i < t; i++) {
        day_data.update();
        _wei.update();
    }
    _wei.print();
    return 0;
}

错误

这个代码只得了35分(洛谷),简单分析一下原因吧:

  • 不应该用上涨的量排序,应该用上涨的比例排序。
  • 金钱和价格不一定整除(大部分时候都不会整除),可能造成浪费。

所以直接贪心其实是得不到全局最优解的。[CSP-J 2019]T3纪念品 题解 by 对面龙哥_第4张图片

背包问题

所以其实这道题应该按照背包问题来做,每一天都以完全背包问题的方法处理一次(
--所以人家的DP才是正解是吧,所以你的贪心其实本来就是错的,只能骗分?
--读书人的代码能叫骗分吗?这叫先拿部分分!)

核心更改

问题出在小伟的购买方式上,所以只要修改小伟的 update函数即可:

void update(const DAY& d = day_data) {
    int* temp = new int[money + 1];
    for (int i = 0; i < n; i++) {
        for (int j = d.price[i]; j <= n; j++) {
            temp[j] = max(temp[j], temp[j - d.price[i]] + d.rise[i]);//背包问题状态转移方程
        }
    }
}

然后由于已经不需要从涨幅高到涨幅低购买,所以对涨幅排序已经不必要了,同时 ITEM 类也不需要了:

class DAY {
    int* price,* rise;
public:
    friend class WEI;
    DAY():price(0),rise(0){}
    ~DAY() {
        delete[]price;
        delete[]rise;
    }
    void set() {
        price = new int[n];
        for (int i = 0; i < n; i++)price[i] = read();
        rise = new int[n];
        memset(rise, 0, n * 4);
    }
    void update() {
        for (int i = 0; i < n; i++) {
            price[i] += rise[i];
            rise[i] = read() - price[i];
        }
        return;
    }
}day_data;

稍作优化

这个代码已经可以AC了,但本着精(shu)益(ju)求(hao)精(kan)的原则,我们还要进行一点(负)优化。

让小伟在购买时可以对每一种物品进行判断,将不会涨价的物品直接排除掉,只要加一个 if 语句即可:

void update(const DAY& d = day_data) {
    int* temp = new int[money + 1];
    memset(temp, 0, (money + 1) * 4);
    for (int i = 0; i < n; i++) {
        if (d.rise[i] > 0){//若涨价量为正
            for (int j = d.price[i]; j <= money; j++) {
                temp[j] = max(temp[j], temp[j - d.price[i]] + d.rise[i]);
            }
        }
    }
    money += temp[money];
}

这样就省去了一部分循环,在洛谷上实测优化前(开启O2优化)s用时在804~855ms之间,
优化后(开启O2优化)是在584~586ms之间,可以说是优化了很多。

总结

这道题其实就是完全背包问题,只要不想偏 -(说的就是我)- 基本都会做,需要思考的地方就是如何发现它是一个完全背包问题 -(而不是贪心)- 。当然如果有的人不会DP就另当别论了 -(不会DP去比什么赛呢)-

完整代码

代码如下:

#include
#include
#include
using namespace std;
int t, n;
inline int max(int a, int b) {
    return a > b ? a : b;
}
inline int read() {
    int x = 0;
    char ch = getchar();
    while (ch > '9' || ch < '0')ch = getchar();
    while (ch >= '0' && ch <= '9') {
        x = (x << 1) + (x << 3) + (ch - 48);
        ch = getchar();
    }
    return x;
}
class DAY {
    int* price,* rise;
public:
    friend class WEI;
    DAY():price(0),rise(0){}
    ~DAY() {
        delete[]price;
        delete[]rise;
    }
    void set() {
        price = new int[n];
        for (int i = 0; i < n; i++)price[i] = read();
        rise = new int[n];
        memset(rise, 0, n * 4);
    }
    void update() {
        for (int i = 0; i < n; i++) {
            price[i] += rise[i];
            rise[i] = read() - price[i];
        }
        return;
    }
}day_data;
class WEI {
    int money;
public:
    WEI() :money(0) {}
    inline void give_money(int x = read()) { money = x; return; }
    void update(const DAY& d = day_data) {
        int* temp = new int[money + 1];
        memset(temp, 0, (money + 1) * 4);
        for (int i = 0; i < n; i++) {
            if (d.rise[i] > 0)for (int j = d.price[i]; j <= money; j++) {
                temp[j] = max(temp[j], temp[j - d.price[i]] + d.rise[i]);
            }
        }
        money += temp[money];
    }
    inline void print() { printf("%d", money); return; }
}_wei;
int main() {
    t = read() - 1;
    n = read();
    _wei.give_money();
    day_data.set();
    for (int i = 0; i < t; i++) {
        day_data.update();
        _wei.update();
    }
    _wei.print();
    return 0;
}

你可能感兴趣的:([CSP-J 2019]T3纪念品 题解 by 对面龙哥)