烽火传递【单调队列优化dp】
题目大意:
1.给出长度为n的数组,要求每m个连续的元素之间必须选一个值作为代价,求该数组的最小代价。
题解思路:
1.显然是线性dp,dp【i】表示选择第 i 个元素时的最小总代价。很明显状态转移方程为 dp[i] = min(dp[j]) + a[i]。(i - m <= j <= i - 1)。但是在求min(dp[j])的时候,我们需要遍历一遍长度为m大小的区间,m极限与n同大。时间负责度0(N^2)。显然会超时。
2.我们需要用单调队列来维护长度为m大小区间内的最小值,队头即为最小值,直接O(1)得到min(dp[j]),就可以避免超时。
代码如下:
1 #include<stdio.h> 2 #include<deque> 3 #include<algorithm> 4 const int MAXN = 2e5 + 100; 5 using namespace std; 6 const int inf = 0x3f3f3f3f; 7 8 int n, m, a[MAXN]; 9 int dp[MAXN]; //表示第 i 个烽火台放置烽火时的最小总代价 10 deque<int> Q; 11 12 int main() 13 { 14 scanf("%d%d",&n, &m); 15 for(int i = 1; i <= n; i ++) 16 scanf("%d", &a[i]); 17 for(int i = 1; i <= m; i ++) //dp以及单调队列初始化 18 { 19 dp[i] = a[i]; 20 while(!Q.empty()) 21 { 22 if(dp[i] < dp[Q.back()])//保证队头保存的是dp值最小的下标 队头到队尾单调递增 23 Q.pop_back(); 24 else 25 break; 26 } 27 Q.push_back(i); 28 } 29 for(int i = m + 1; i <= n; i ++) 30 { 31 while(!Q.empty()) 32 { 33 if(i - m > Q.front()) //保证队头位于枚举范围内,即是在 [i - m, i - 1]范围内 34 Q.pop_front(); 35 else 36 break; 37 } 38 dp[i] = dp[Q.front()] + a[i]; 39 while(!Q.empty()) 40 { 41 if(dp[i] < dp[Q.back()]) 42 Q.pop_back(); 43 else 44 break; 45 } 46 Q.push_back(i); 47 } 48 int ans = inf; 49 for(int i = n; i > n - m; i --) 50 ans = min(ans, dp[i]); 51 printf("%d\n", ans); 52 return 0; 53 } 54 /* 55 5 3 56 1 2 5 6 2 57 58 4 59 */