HDU 2037 今年暑假不AC 个人题解(贪心||动态规划)

HDU 2037 今年暑假不AC 个人题解(贪心||动态规划)

训练的时候遇见的一道题,个人感觉收获蛮多的。


题目传送门

题目大意:
  • 给你一张电视节目单时刻表,包含每个节目的开始时间和结束时间,求最多能看多少个节目。
样例输入

第一行是节目的个数n,下面n行是每个节目的开始时间(from)和结束时间(to)。n==0时结束输入。

12
1 3
3 4
0 7
3 8
15 19
15 20
10 15
8 18
6 12
5 10
4 14
2 9
0

样例输出

5


贪心算法

 看到这个题目我首先想到的是贪心,先按照每一个节目的结束时间(to)从小到大排序,然后从最先结束的节目开始,依次向后查找最近开始的节目(也就是这两个节目之间的等待时间最短),然后从这个找到的节目开始,按照刚才的步骤继续向后查找,即可以找到最多看多少个节目。
代码如下:

#include <bits/stdc++.h>
using namespace std;

typedef struct NODE
{
    int from, to;
} Node;

int main(void)
{
    cin.tie(NULL);
    ios::sync_with_stdio(false);
    int n, res;
    while (cin >> n, n)
    {
        Node *node = new Node[n];
        for (int i = 0; i < n; i++)
            cin >> node[i].from >> node[i].to;
        sort(node, node + n, [](Node &a, Node &b) { return a.to < b.to; });
        res = 1;		//最开始的节目
        for (int j = 1, i = 0; j < n; j++)
            if (node[j].from >= node[i].to)
            {
                i = j;
                res++;
            }
        cout << res << endl;
        delete node;
    }
    return 0;
}

 这样就可以AC了。但是换一种角度看,如果将挑选电视节目看成工作安排,观看一个电视节目的价值是1,即想方设法找出在一定时间内价值总量最高的工作序列,这样就成了一个动态规划的问题。可能用动态规划求解不是最合适的算法,但这样做的好处是如果每一个电视节目的价值不一样的话,也可以找到总价值最高的电视节目顺序(工作序列)。(现学现卖类型。)


动态规划(DP)

 解决这种动态规划问题主要的思路是“选或不选”。类似于初中的分类讨论,我们在对一个节目进行决策时,需要比较“看”这个节目的总价值与“不看”这个节目的总价值,取二者中比较大的情况即可。
 现在的问题在于怎么对这个实际问题建立数学模型,使其可以用计算机常用的方法——函数表示出来。首先我们先将所有数据以结束时间(to)从小到大进行排序,排序完之后将数据可视化如下:(Excel画的…)
在这里插入图片描述
 上图中,蓝色框框表示一个电视节目,蓝色框框中的白色数字表示自己规定的节目序号,黑色小数字表示其左侧竖线代表的时间。我们以9号节目为例,进行“选或不选”的分析过程:

  • 不选:如果不选9号,那么9号及其之前的最大价值量与8号相同;
  • 选:如果选9号,那么9号及其之前的最大价值量为9号本身价值量 + 6号及其之前的最大价值量。

那么“6号”是怎么来的呢?分析数据或从图中可以看出,6号是9号发生之前最近发生的事件。然后我们就可以得到如下的公式:
o p t [ i ] = m a x { o p t [ i − 1 ] , 不 选 v a l [ i ] + o p t [ p r e v [ i ] ] , 选 opt[i]=max\begin{cases} opt[i-1]&,不选\\ val[i]+opt[prev[i]]&,选 \end{cases} opt[i]=max{opt[i1]val[i]+opt[prev[i]],,
其中 o p t [ i ] opt[i] opt[i] 表示节目 i i i 及其之前的最大价值序列 ( o p t i m i z a t i o n ) (optimization) (optimization) p r e v [ i ] prev[i] prev[i] 表示事件 i i i 之前的最近发生事件, v a l [ i ] val[i] val[i] 表示事件 i i i 的价值。我们采用的是空间换时间的做法(不使用递归)。这样,我们只需要建立 o p t [ i ] 、 p r e v [ i ] opt[i]、prev[i] opt[i]prev[i] 即可。

 在此题目中,我将一个电视节目保存为一个数组,包含如下变量:

typedef struct NODE
{
    int from, to;
    int prev = 0, val = 1int opt = val;
} Node;

直接将 o p t 、 p r e v opt、prev optprev 数组包含进节目属性。

  • 建立 p r e v prev prev 数组:
for (int i = 1; i <= n; i++)
	for (int j = i - 1; j > 0; j--)		// 从后向前遍历,结束时间递减
	    if (node[j].to <= node[i].from)
	    {
	        node[i].prev = j;		// j 为 i 之前最近结束事件的序号
	        break;
	    }
  • 建立 o p t opt opt 数组:
for (int i = 1; i <= n; i++)
	node[i].opt = max(node[i - 1].opt, node[i].val + node[node[i].prev].opt);

这里需要注意的一点是 n o d e [ 0 ] node[0] node[0] 表示一个空事件,应该特殊赋值:

//node[0].val = 0;
node[0].opt = 0;					// 空事件的特别赋值

最后,我们再遍历所有节目的 o p t opt opt 找出最大值即可。完成代码如下,时间复杂度 O ( n 2 ) O(n^2) O(n2)

#include <bits/stdc++.h>
using namespace std;

typedef struct NODE
{
    int from, to;
    int prev = 0, val = 1;
    int opt = val;
} Node;

int main(void)
{
    cin.tie(NULL);
    ios::sync_with_stdio(false);
    int n;
    while (cin >> n, n)
    {
        Node *node = new Node[n + 1];
        int res = 0;
        for (int i = 1; i <= n; i++)
            cin >> node[i].from >> node[i].to;
        sort(node + 1, node + n + 1, [](Node &a, Node &b) { return a.to < b.to; });
        // node[0].val = 0;
        node[0].opt = 0;
        for (int i = 1; i <= n; i++)
            for (int j = i - 1; j > 0; j--)
                if (node[j].to <= node[i].from)
                {
                    node[i].prev = j;
                    break;
                }
        for (int i = 1; i <= n; i++)
            node[i].opt = max(node[i - 1].opt, node[i].val + node[node[i].prev].opt);
        for (int i = 1; i <= n; i++)
            res = max(res, node[i].opt);
        cout << res << endl;
        delete node;
    }
    return 0;
}


 如有错误还望指出!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

A91A981E

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值