动态规划:洛谷P5858 「SWTR-03」Golden Sword | 运用 【单调队列】+【滚动数组】
洛谷P5858 「SWTR-03」Golden Sword
洛谷的一题绿题,一定要看清楚题目我画红线的要按顺序投入,不按顺序投入我做不出来,那时候没看到想了好久555.直接想到构建二维dp数组,dp[i][j],i代表投进去第i种,j代表此时锅里有几种东西,所以可以得到状态转移方程:dp[i][j]=max(dp[i-1][k])+j*a[i] k就是上一层的锅里的数量,范围是j-1到min(j+s-1,w);这个k的范围非常重要,下面在我的代码里有讲到取值的原因。所以K的取值可以用一层循环,i j的遍历可以有两层循环,时间复杂度就是O(N3),显然容易超时,所以我们需要改进。观察到dp[i][j]=max dp[i-1][k] k的取值区间其实就是一直随着j移动的一个窗口,像单调队列的经典题型,移动窗口,所以我们可以用单调队列来优化里面的j 和 k循环,把里面的时间复杂度从n2降为n 则最终的时间复杂度是n3.
一、O(n2)
1 //洛谷P5858 「SWTR-03」Golden Sword 2 #include<iostream> 3 #include<cmath> 4 #include<algorithm> 5 #include<cstring> 6 using namespace std; 7 long long a[5005]; 8 long long dp[5005][5005];//表示刚投入的是i 并且此时锅里面有j种的耐久度 9 long long ans = -0x3f3f3f3f3f3f3f3f;//让ans尽可能小,便于比较得出答案 10 int main() 11 { 12 int s, w, n; 13 cin >> n >> w >> s; 14 for (int i = 1; i <= n; ++i)cin >> a[i]; 15 16 memset(dp, 0xcf, sizeof(dp)); 17 dp[0][0] = 0; 18 //先把dp数组初始化为-的最小 并且让dp[0][0]=0 19 //便于后面dp,保证了第一个投进去的时候从锅里面是空的状态dp 第二个投进去的时候从上一个状态转移 20 for (int i = 1; i <= n; ++i) 21 { 22 for (int j = 1; j <= w; ++j) 23 { 24 //投进去的时候锅里面有j个的可能 可能是从dp[i-1][k]转移来的 25 //k最小可能是j-1 一个都没拿出来投进去,最多可能是j-1+s拿走了s个并且投入了1个 等于j 所以上一个状态是j+s-1 26 for (int k = j - 1; k <= min(w, j + s - 1); ++k) 27 { 28 dp[i][j] = max(dp[i][j], dp[i - 1][k] + a[i] * j); 29 } 30 31 32 } 33 } 34 for (int i = 1; i <= w; ++i)//遍历最后一个是n 锅里面有i种的dp值 找到最优解 35 { 36 ans = max(ans, dp[n][i]); 37 } 38 cout << ans; 39 return 0; 40 41 }
显然会超时:
二、单调队列 O(N2)
1 //洛谷P5858 「SWTR-03」Golden Sword 2 #include<iostream> 3 #include<cmath> 4 #include<algorithm> 5 #include<cstring> 6 using namespace std; 7 long long a[5005]; 8 long long dp[5005][5005];//表示刚投入的是i 并且此时锅里面有j种的耐久度 9 long long ans = -0x3f3f3f3f3f3f3f3f;//让ans尽可能小,便于比较得出答案 10 int pos[5005];//单调队列下标数组 11 long long dl[5005];//单调队列 队列数组 12 int main() 13 { 14 int s, w, n; 15 cin >> n >> w >> s; 16 for (int i = 1; i <= n; ++i)cin >> a[i]; 17 18 memset(dp, 0xcf, sizeof(dp)); 19 dp[0][0] = 0; 20 //先把dp数组初始化为-的最小 并且让dp[0][0]=0 21 //便于后面dp,保证了第一个投进去的时候从锅里面是空的状态dp 第二个投进去的时候从上一个状态转移 22 for (int i = 1; i <= n; ++i) 23 { 24 //单调队列 并运用滚动数组 25 int l = 1, r = 1;//每次都初始化队头和队尾为1 26 dl[l] = dp[i - 1][w]; 27 pos[l] = w; 28 for (int j = w; j >= 1; --j) 29 { 30 if (pos[l] > j + s - 1 && l <= r)l++; 31 while (dp[i - 1][j - 1] > dl[r]&&l<=r)r--; 32 dl[++r] = dp[i - 1][j - 1]; 33 pos[r] = j - 1; 34 dp[i][j] = a[i] * j+dl[l]; 35 } 36 37 38 } 39 for (int i = 1; i <= w; ++i)//遍历最后一个是n 锅里面有i种的dp值 找到最优解 40 { 41 ans = max(ans, dp[n][i]); 42 } 43 cout << ans; 44 return 0; 45 46 }
这题能用滑动窗口的思路是:
完美通过。