动态规划:洛谷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 }

 

这题能用滑动窗口的思路是:

 

 

 

 

 

 完美通过。

posted @ 2022-04-12 09:54  朱朱成  阅读(95)  评论(0编辑  收藏  举报