2021CCPC新疆省赛-B
    
  
    
    
    
    
      
    
    
      
    
    题目链接
这是一道DP的题目,但赛中的大部分时间我都将其当成了贪心的题目来写…结果就是不论怎么写都永远有未考虑的情况,将所有情况都考虑到的代码复杂度又是惊人的。赛后队友不知在哪看到了一个原则:能用DP来写的题目要尽量用DP来写。
这一题的状态选取非常别扭。看完题的第一想法是将前n天所能取得的最优解作为状态,但这前面天数的选择会对后面天数的选择有影响,而且前n天的最优解不一定比前n-1天的最优解更大,将dp求完之后还要找最大值。不是不能写,但是代码会比较复杂。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
   | #include <iostream> #include <cstdio> #include <cstring> #include <cstdlib> #include <cmath> #include <algorithm> #include <map> #include <set> #include <queue> #include <stack> #include <cctype> using namespace std; long long a[100005], dp[300005]; int main() {     int n, m, d, i;     long long res = 0;     scanf("%d%d%d", &n, &d, &m);     for (i = 1; i <= n; i++)     {         scanf("%lld", &a[i]);         if (a[i] <= m)         {             dp[i] += a[i];             dp[i + 1] = max(dp[i + 1], dp[i]);             if (dp[i] > res)                 res = dp[i];             continue;         }         dp[i + 1] = max(dp[i + 1], dp[i]);         dp[i] += a[i];         if (i + d + 1 <= n)             dp[i + d + 1] = max(dp[i + d + 1], dp[i]);         if (dp[i] > res)             res = dp[i];     }     printf("%lld\n", res);     return 0; }
   | 
 
大部分人选取的状态是:最后i天所能造成的最大伤害。状态转移也是从最后一天往前进行的。倒着来的好处是:当某一天的伤害超限,对后面的天数造成影响时,不需要去修改其它的值,只要在对这一天进行状态转移的时候不将其后一天的状态转移过来就行了。
倒序处理的代码无论如何都比较难以理解,先写了一个dp数组开到二维的版本:dp[0][i]表示后n位不取当前位所能取得的最大值,dp[1][i]表示后n位取了当前位所能取得的最大值。不难写出代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
   | #include <iostream> #include <cstdio> #include <cstring> #include <cstdlib> #include <cmath> #include <algorithm> #include <map> #include <set> #include <queue> #include <stack> #include <cctype> using namespace std; long long dp[2][200005], a[200005]; int main() {     int n, m, d, i;     scanf("%d%d%d", &n, &d, &m);     for (i = 1; i <= n; i++)         scanf("%lld", &a[i]);     for (i = n; i > 0; i--)     {         dp[0][i] = max(dp[0][i + 1], dp[1][i + 1]);         if (a[i] <= m)             dp[1][i] = max(dp[0][i + 1], dp[1][i + 1]) + a[i];         else             dp[1][i] = a[i] + max(dp[0][i + d + 1], dp[1][i + d + 1]);     }     printf("%lld", max(dp[0][1], dp[1][1]));     return 0; }
   | 
 
注意到:当用到已经处理过的状态时,对应的dp[0]和dp[1]总是同时出现,且只取最大值。所以dp不论取与不取,仅需要保存最大值,所以只用开一维数组就行了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
   | #include <iostream> #include <cstdio> #include <cstring> #include <cstdlib> #include <cmath> #include <algorithm> #include <map> #include <set> #include <queue> #include <stack> #include <cctype> using namespace std; long long dp[200005], a[200005]; int main() {     int n, m, d, i;     scanf("%d%d%d", &n, &d, &m);     for (i = 1; i <= n; i++)         scanf("%lld", &a[i]);     for (i = n; i > 0; i--)     {         if (a[i] <= m)             dp[i] = a[i] + dp[i + 1];         else             dp[i] = max(dp[i + 1], dp[i + d + 1] + a[i]);     }     printf("%lld", dp[1]);     return 0; }
   |