Luogu P1280.尼克的任务

文章详细介绍了如何使用动态规划和Dijkstra算法解决编程竞赛题目LuoguP1280——尼克的任务。动态规划方法中,文章阐述了状态定义、转移式的推导、递推顺序、边界确定以及结果输出的过程。而对于Dijkstra算法的应用,文章说明了如何构建图并进行最短路求解。最后给出了两种方法的代码实现。
摘要由CSDN通过智能技术生成

Luogu P1280.尼克的任务


原题点这里

思路

方法一:动态规划


这是一道动态规划的题目。

步骤主要分 5 5 5 步:

  1. 状态的定义
  2. 转移式的推到
  3. 递推顺序的判定
  4. 边界的确定
  5. 结果的输出

下面,我们针对这道题,细细地讲解一下每一个步骤

一、状态的定义


这道题的状态最简单,又直白——就是 f i f_i fi 表示 i ∼ n i \sim n in的最大休息时间。
对于状态的定义大家可以多去试一试,不行了就换一种思路,做多了就会有思路,一般的定义所具备的都是至少有一个 f i f_i fi 代表是第 i i i 个。

二、转移式的推到


一般情况下,如果状态找对了,那么转移式便会呼之欲出了。

这道题比较难想,运用到分类讨论的思想!

情况1:

如果时间点 i i i 没有任务,那么 f i = f i + 1 + 1 f_i = f_{i + 1} + 1 fi=fi+1+1,即上一个时间段的最大休息时间加上时间点 i i i 的歇息时间。

情况2:

如果时间点 i i i 是某一个任务的开始点,那么 f i = f i + t a s k i , j f_i = f_{i + task_{i, j}} fi=fi+taski,j(其中 t a s k i , j task_{i, j} taski,j 表示某个任务从 i i i 时间点开始,持续时间为 j j j

综上所述:
f i = { f i + 1 + 1 max ⁡ ( f i + t a s k i , j ) f_i=\left\{\begin{matrix} & f_{i + 1} + 1\\ & \max(f_{i+task_{i,j}}) \end{matrix}\right. fi={fi+1+1max(fi+taski,j)

三、递推顺序的判定


这一部分就很简单了,对于这道题因为我们发现一定会要么相同,要么由后面的状态所转移。

为了保持无后效性,我们需采用由大到小的顺序枚举

四、边界的确定


这道题大家可以想一想, f n f_n fn的值是多少呢?根据定义,即 n ∼ n n \sim n nn的最大休息时间,休息时间就是 0 0 0。所以初始化数组就是0,故我们不需要确定边界!

五、结果的输出


根据我们的状态的定义,不难确定出答案就是 f 1 f_1 f1

代码


#include <iostream>
#include <vector>

using namespace std;

const int N = 1e4 + 10;

int n, k;
int p, t;
vector<int> task[N];
int f[N];

int main()
{
	cin >> n >> k;
	
	for (int i = 1; i <= k; i ++)
		cin >> p >> t, task[p].push_back(t);
		
	for (int i = n; i >= 1; i --)
		if (task[i].size())
			for (auto c : task[i])
				f[i] = max(f[i], f[i + c]);
		else
			f[i] = f[i + 1] + 1;
			
	cout << f[1] << endl;
}

方法二:最短路(Dijkstra)


对于每一个任务,可以连接一条 p p p p + t p+t p+t 边长为 t t t 的边,对于那些没有连接的点,我们就将他们与下一个时间点相连,边权为 0 0 0。之后,跑一遍最短路,就是我们要花费的最小时间。注意,最后返回的是 d i s t n + 1 dist_{n+1} distn+1,而不是 d i s t n dist_n distn。因为任务是从 p + t − 1 p+t-1 p+t1 结束,最后 p + t p+t p+t 可能会超出 n n n,所以输出 d i s t n + 1 dist_{n+1} distn+1

代码


#include <iostream>
#include <queue>
#include <cstring>

using namespace std;

const int N = 2e4 + 10;
typedef pair<int, int> PII;

int n, k;
int p, t;
int h[N], e[N], ne[N], w[N], idx;
int dist[N], st[N];

void add(int a, int b, int c)
{
    e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx ++;
}

int Dijkstra()
{
    memset(dist, 0x3f, sizeof dist);
    priority_queue<PII, vector<PII>, greater<PII>> heap;
    heap.push({0, 1});
    dist[1] = 0;
    
    while (heap.size())
    {
        auto t = heap.top();
        heap.pop();
        
        int u = t.second, dis = t.first;
        
        if (st[u]) continue;
        st[u] = 1;
        
        for (int i = h[u]; ~i; i = ne[i])
            if (dist[e[i]] > dis + w[i])
            {
                dist[e[i]] = dis + w[i];
                heap.push({dist[e[i]], e[i]});
            }
    }
    
    return dist[n + 1];
}

int main()
{
    memset(h, -1, sizeof h);
    
    cin >> n >> k;
    
    for (int i = 1; i <= k; i ++)
        cin >> p >> t, add(p, p + t, t);
        
    for (int i = 1; i <= n; i ++)
        if (h[i] == -1)
            add(i, i + 1, 0);
            
    cout << n - Dijkstra() << endl;
}

最后祝大家早日——
在这里插入图片描述

纵有狂风平地起,我欲乘风破万里!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值