【SSL】2883 &【JZOI】1771烽火传递(单调队列)

【SSL】2883 &【JZOI】1771烽火传递(单调队列)

Time Limit:1000MS
Memory Limit:512000K

Description

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

Input

第一行:两个整数 N,M。其中N表示烽火台的个数, M 表示在连续 m 个烽火台中至少要有一个发出信号。接下来 N 行,每行一个数 Wi,表示第i个烽火台发出信号所需代价。

Output

一行,表示答案。

Sample Input

5 3 
1 
2 
5 
6 
2

Sample Output

4

Hint

对于50%的数据,M≤N≤1,000 。 对于100%的数据,M≤N≤100,000,Wi≤100。

思路

由于题目要求连续m个烽火台中至少要有一个发出信号,很容易得出DP转移方程:F[i]=min(F[j]:i−m<j<i)+a[i]最直接的方法是枚举状态,对于每一个i,我们在i-m+1到i-1中寻找一个最小的F[j]进行状态转移,枚举状态的时间复杂度是O(n),寻找最小值的状态时间复杂度是O(n),因此这种方法的复杂度是O(n^2)。题目的是数据范围是n<=100000,显然超时。
那么怎么用单调队列优化呢?
上图中,状态枚举到i,当m=4时,我们要做的就是在i-3到i-1中找到最小的F[j],那么枚举到i+1时,我们要做的就是要在i-2到i中找到最小的F[j]。上图中我们可以看出,要寻找最小值的区间向后移动了一位,也就是F[i-m+1]的值被抛弃,F[i-1]的值被加入。这里就可以用单调队列处理了,F[i-1]是插队的数据,F[i-1]有资格插队是因为它更优且更靠近i,比它更差的数将被它取代,保留那些数据没有任何好处。而那些已经不再维护区间之外的就不必再对其进行维护,出队即可。

代码

#include<iostream>
#include<cstdio>
#include<deque>
using namespace std;
int ans[100010];
deque<int>q;
int main()
{
    
    
	int i,n,m,answer=100000000;
	scanf("%d%d",&n,&m);
	for(i=1;i<m;i++)
	{
    
    
		scanf("%d",&ans[i]);
		for(;!q.empty()&&ans[i]<=ans[q.back()];q.pop_back());//删除大于x的数
		q.push_back(i);
	}
	for(;i<=n;i++)
	{
    
    
		scanf("%d",&ans[i]);
		for(;!q.empty()&&q.front()<i-m;q.pop_front());//删除不在范围内的数
		ans[i]+=ans[q.front()];
		for(;!q.empty()&&ans[i]<=ans[q.back()];q.pop_back());//删除大于x的数
		q.push_back(i);
	}
	for(i=n-m+1;i<=n;i++)
		answer=min(answer,ans[i]);
	printf("%d",answer);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_46975572/article/details/113104131