nkoj 2151
Description
烽火台又称烽燧,是重要的防御设施,一般建在险要处或交通要道上。一旦有敌情发生,白天燃烧柴草,通过浓烟表达信息:夜晚燃烧干柴,以火光传递军情。在某两座城市之间有n个烽火台,每个烽火台发出信号都有一定的代价。为了使情报准确的传递,在m个烽火台中至少要有一个发出信号。现输入n、m和每个烽火台发出的信号的代价(正整数),请计算总共最少需要花费多少代价,才能使敌军来袭之时,情报能在这两座城市之间准确的传递!!!
Input
第一行有两个数n,m(1<=n,m<=1000000)分别表示n个烽火台,在m个烽火台中至少要有一个发出信号。
第二行为n个数,表示每一个烽火台的代价。
Output
一个数,即最小代价。
Sample Input
5 3
1 2 5 6 2
Sample Output
4
分析:
应该算是滑动窗口模型,再结合动态规划。
状态: f[i]表示从1到i满足题意,并选中第i个烽火台的最小代价。
那么 f[i]= s[i]+min(s[j]) (i-m<=j <= i -1)
min(s[j])就利用滑动窗口的思想计算了。
代码如下:
#include<cstdio> #include<iostream> using namespace std; const int maxn=1000000+5; int n,m,s[maxn],f[maxn],pos[maxn],q[maxn]; //多关键字队列,q为烽火台的代价,pos为下标; inline void _read(int &x){ char ch=getchar(); bool mark=false; for(;!isdigit(ch);ch=getchar())if(ch=='-')mark=true; for(x=0;isdigit(ch);ch=getchar())x=x*10+ch-'0'; if(mark)x=-x; } int main(){ int i,j,ans=2e9,front,rear; _read(n);_read(m); for(i=1;i<=n;i++)_read(s[i]); front=rear=1; rear++; //一定要先push一个空元素(血的教训) ; for(i=1;i<=n;i++){ //滑动窗口最小值,维护q递增,且首尾下标差不超过m; while(front!=rear&&i-pos[front]>m)front++; // 下标差大于m f[i]=s[i]+q[front]; //此时,q[front]就是min(s[j])(i-m<=j<=i-1); while(front!=rear&&f[i]<q[rear-1])rear--; q[rear]=f[i];pos[rear]=i;rear++; //相当于push } for(i=n;i>n-m;i--)ans=min(ans,f[i]); //找答案 printf("%d",ans); }
类似的题:nkoj 3661 修剪草坪:
输入大致相同,n个数中若不能连续选择k个数,那么选出数的最大和是多少?
如果关注题意的反面,就会发现这和烽火传递其实是一道题:
反面:每m+1只牛中,至少有一只不选,求不选的和的最小值。不就是烽火传递吗?
stl版
#include<iostream> #include<cstdio> #include<deque> #define inf 99999999999999LL using namespace std; long long a[1000005]; struct node{ long long n; long long w; node(long long n,long long w):n(n),w(w){} }; deque<node>q; long long f[1000005]; int main(){ long long n,m,i,j,k,ans; ans=inf; cin>>n>>m; for(i=1;i<=n;i++){ scanf("%d",&a[i]); } for(i=0;i<=n;i++){ f[i]=a[i]+q.front().w; while(q.size()&&q.back().w>f[i])q.pop_back(); q.push_back(node(i,f[i])); while(q.size()&&q.front().n<i-m+1)q.pop_front(); } for(i=n;i>n-m;i--){ ans=min(ans,f[i]); } cout<<ans; }