烽火台又称烽燧,是重要的军事防御设施,一般建在险要或交通要道上。一旦有敌情发生,白天燃烧柴草,通过浓烟表达信息;夜晚燃烧干柴,以火光传递军情,在某两座城市之间有 n 个烽火台,每个烽火台发出信号都有一定代价。为了使情报准确地传递,在连续 m 个烽火台中至少要有一个发出信号。请计算总共最少花费多少代价,才能使敌军来袭之时,情报能在这两座城市之间准确传递。
Input
第一行:两个整数 N,M。其中N表示烽火台的个数, M 表示在连续 m 个烽火台中至少要有一个发出信号。接下来 N 行,每行一个数 Wi,表示第i个烽火台发出信号所需代价。
Output
一行,表示答案。
Sample Input
5 3
1
2
5
6
2
Sample Output
4
Data Constraint
对于50%的数据,M≤N≤1,000 。 对于100%的数据,M≤N≤100,000,Wi≤100。
分析:
定义f[i]为点燃第i个烽火时前i个烽火满足条件的最小值。则状态转移方程为:
f[i]=min(f[j])+a[i]. 其中 i-m<=j<=i-1;
由此可以得出一般的解法:
#include <iostream>//烽火传递问题。
using namespace std;
const int inf=1e6+7;
int a[inf];
int f[inf];
/*状态转移方程 :
定义f[i]: 点燃第i个烽火,并且前i个满足条件。
则:f[i]=(min)f[j]+a[i] i-m<=j<=i-1; 状态转移方程。
*/
int main()
{
int n,m;
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
f[0]=0;
for(int i=1;i<=m;i++)
f[i]=a[i];
for(int i=m+1;i<=n;i++)
{
long long themax= ~(1<<31);
for(int j=i-m;j<=i-1;j++)
{
if(f[j]<themax)
themax=f[j];
}
f[i]=themax+a[i];
}
long long tmax=1<<30;
for(int i=n;i>n-m;i--)
{
if(f[i]<tmax)
tmax=f[i];
}
cout<<tmax<<endl;
return 0;
}
但是从以上程序中也可以看出复杂度为O(n的平方),对应题目而言显然是不行的,并且注意到是线性结构,求其最小值,所以可以使用单调队列进行优化。单调队列我认为和单调函数差不多,不是单调递增就是单调递减,只要在入队的时候保持队列的单调性就行了,并且弹出比入队元素大的元素就可以了。
代码:
#include <iostream> //单调队列+dp
using namespace std;
const int inf=1e6+7;
int a[inf];
int que[inf];
int f[inf];
/*
转移方程:f[i]=min(f[j])+a[i];
是从f【i】入队的吧。
*/
int main()
{
int n, m;
int head=1,tail=0;
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=0;i<n;i++) //其中的过程,其中的状态转移过程。
{
while(tail>=head && f[i]<=f[que[tail]])tail--; //d出队。
que[++tail]=i;
while(tail>=head && que[head]<i+1-m)head++;
f[i+1]=f[ que[head] ]+a[i+1];
}
int ans=~(1<<31);
for(int i=n;i>n-m;i--)
ans=min(f[i],ans);
cout<<ans<<endl;
return 0;
}