烽火传递

烽火传递(jdoj-1006)

    题目大意:n个烽火台,任意的连续的m个烽火台必有一个是点燃的,点燃一个烽火台有一个单独的代价,求:最少代价。

    注释:n , m<=100000 代价<=100

      想法:开始误以为是最短路!开个玩笑,咳咳。这题一眼看出是道dp题,状态转移方程也级水,设dp [ i ] 表示点燃第 i 烽火台所需要的最少代价。那么,dp [ i + 1 ] = dp [ j ]( i-m+1<=j<=i ) + a [ j ] , a[]是代价数组。这题,是不是就切了!?!哇,大水题!等等,时间复杂度是O(10^10),完蛋了,过不去,我们先想想如何优化:对于每一个需要更新的值,我们都比较了m-1次,这m-1次中,我们重复比较了太多,所以,导致时间超限。我们只需要维护一段定长区间之内的最小值即可:在此,介绍一种数据结构——单调队列。这玩意儿是一种优化手段,我们维护一个队列,这东西是单调的。和单调栈类似,我们从后面加入元素(队列递增),如果遇到比这个元素大的,直接弹出,理由:在未来的每一次更新中,只要存在被弹出元素,就一定存在那个刚刚加入队列的元素,所以这个被弹出元素是对答案没有任何贡献的,所以,我们将其弹出,以此类推,我们就可以维护处一个单调的队列。而单调队列另一个需要维护的就是我们还需要维护这个队列是单调的,所以,这个队列维护的可以是数组的下标,每次更新队首,如果队首的元素的下标已经过时,直接弹出即可。反之,队首元素即使最优解。由于C++给予的STL虽然很方便,凡是有点慢,所以我们选择又数组实现。而这道烽火传递几乎可以说的上是单调队列裸题,博主比较菜,先做做入门题也好啊!!

    最后,附上丑陋的代码......

 1 #include <iostream>
 2 #include <cstdio>
 3 using namespace std;
 4 int dp[100010];
 5 int a[100010];
 6 int q[100010];
 7 int main()
 8 {
 9     int n,m;
10     scanf("%d%d",&n,&m);
11     int head=1;//表示队首的下标。
12     int tail=1;//表示队尾的下标。
13     for(int i=1;i<=n;i++)
14     {
15         scanf("%d",&a[i]);
16     }
17     for(int i=1;i<=n;i++)
18     {
19         dp[i]=dp[q[head]]+a[i];
20         while(tail>=head&&dp[i]<=dp[q[tail]])//每次更新都表示弹出队尾元素。
21             tail--;
22         q[++tail]=i;//弹完后,我们保证这个队列是单调的且新加入的元素一定是队尾元素。
23         while(q[head]<i+1-m)//判断队首元素是否合法,如果不合法,将其弹掉。
24             head++;
25     }
26     printf("%d",dp[q[head]]);//这一步比较经典。我们维护的单调队列中,队首元素一定是合法的(在最后的m个烽火台之内),
27     //所以我们选择这其中的最小者更新即可
28     return 0;
29 }

    小结:错误

      1.head和tail的值比较重要,注意即可

      2.最后一步比较冒险,需要证明,也可以将最后m个dp松弛即可。

  转载请注明:http://www.cnblogs.com/ShuraK/p/7871623.html——为经博主允许,严禁转载

原文地址:https://www.cnblogs.com/ShuraK/p/7871623.html