单调对列,烽火传递思路及题解

一、题目描述

烽火台又称烽燧,是重要的防御设施,一般建在险要处或交通要道上。一旦有敌情发生,白天燃烧柴草,通过浓烟表达信息:夜晚燃烧干柴,以火光传递军情。在某两座城市之间有n个烽火台,每个烽火台发出信号都有一定的代价。为了使情报准确的传递,在m个烽火台中至少要有一个发出信号。现输入n、m和每个烽火台发出的信号的代价,请计算总共最少需要花费多少代价,才能使敌军来袭之时,情报能在这两座城市之间准确的传递。

二、输入输出样例

5 3 
1 2 5 6 2
4

三、思路

1.方法——动态规划

这道题要求我们求最小值,数据又很大,我们肯定想到会用动态规划算法解题。根据我们定义线性动态规划的常见操作,可以得到如下定义:定义dp[i]表示点燃第i个烽火台(前面已经点燃)的最小花费。经过对题目的分析后,可以得到动态转移方程:dp[i] = min(dp[j]) + c[i] (i - m <= j <= i - 1, c[i]表示点燃第i个烽火台的花费)。

2.优化——单调队列

但是,问题来了。如果我们用暴力枚举动态转移方程中的j的话,时间复杂度O(nm),超时!怎么办?考虑优化。注意到j是有范围的,我们可以用单调队列进行优化

单调队列模板(取最大)

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

struct node
{
	int v, k;
	node(){}
	node(int x, int y)
	{
		v = x;
		k = y;
	}
};
int n, m;

int dddl() //单调队列 
{
	node q[300002];
	int f = 1, l = 1; 
	for(int i = 1; i <= n; i++)
	{
		int x;
		scanf("%d", &x);
		while(f < l && i - q[f].k >= m)
			f++;
		while(f < l && dp[i] >= q[l - 1].v)
			l--;
		q[l] = node(x, i);
		l++;
	}
	return q[f].v;
}

int main()
{
	scanf("%d %d", &n, &m);
	dddl();
	return 0;
}

3.注意事项

答案是min(dp[i]) (n - m + 1 <= i <= n),所以最后要再遍历一遍dp数组的最后m位。

四、最终的标程

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

struct node
{
	int v, k;
	node(){}
	node(int x, int y)
	{
		v = x;
		k = y;
	}
};
node q[300002];
int dp[300002];
int n, m, f = 1, l = 1; 

int main()
{
	scanf("%d %d", &n, &m);
	q[l] = node(0, 0);
	l++;
	for(int i = 1; i <= n; i++)
	{
		int x;
		scanf("%d", &x);
		while(f < l && i - q[f].k > m)
			f++;
		dp[i] = q[f].v + x;
		while(f < l && dp[i] <= q[l - 1].v)
			l--;
		q[l] = node(dp[i], i);
		l++;
	}
	int ans = 0x7fffffff;
	for(int i = n - m + 1; i <= n; i++)
		ans = min(ans, dp[i]);
	printf("%d", ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值