[离散化dp] NC19809 growth

    技术2022-07-10  127

    题面

    link    一开始有属性a和b,这两个属性初始的时候均为0,每一天可以让a涨1点或b涨1点。我们共有 n n n 种奖励,第i种奖励有 x i , y i , z i x_i,y_i,z_i xiyizi 三种属性,若 a ≥ x i a≥ x_i axi b ≥ y i b≥ y_i byi,则弱弱在接下来的每一天都可以得到 z i z_i zi 的分数。    问 m m m 天以后弱弱最多能得到多少分数,其中 1 ≤ n ≤ 1000 ,   1 ≤ m ≤ 2 e 9 ,   x i , y i ≤ 1 e 9 1 ≤ n ≤ 1000, \ 1 ≤ m ≤ 2e9, \ x_i, y_i ≤ 1e9 1n1000 1m2e9, xi,yi1e9。   

    分析

      当 m m m 很小时,可以想到动态规划的方法。若 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示 i + j i + j i+j 天且 a = i ,   b = j a = i, \ b = j a=i, b=j 时得到的最大奖励,那么这个值肯定和之前状态有关, 若用 v [ i ] [ j ] v[i][j] v[i][j] 表示 a = i ,   b = j a = i, \ b = j a=i, b=j 时这一天可以得到的奖励,那么可以得到这样的状态转移方程: d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − 1 ] ) + v [ i ] [ j ] dp[i][j] = max(dp[i-1][j], dp[i][j-1]) + v[i][j] dp[i][j]=max(dp[i1][j],dp[i][j1])+v[i][j]    而对于 v [ i ] [ j ] v[i][j] v[i][j], 我们可以用前缀和的方法进行求解,若是初始时 v [ i ] [ j ] v[i][j] v[i][j] 表示 x = i ,   b = j x = i, \ b = j x=i, b=j 的属性的 z z z 值,可以得到这样的状态转移方程: v [ i ] [ j ] + = v [ i − 1 ] [ j ] + v [ i ] [ j − 1 ] − v [ i − 1 ] [ j − 1 ] v[i][j] += v[i-1][j] + v[i][j-1] - v[i-1][j-1] v[i][j]+=v[i1][j]+v[i][j1]v[i1][j1]   这个可以类比一维的求前缀和得到,也可以简单地类比面积计算得到:   若 S S S 初始表示右上那一块小的,若要表示大长方形面积,对应 v [ i ] [ j ] v[i][j] v[i][j], S 1 S_1 S1 对应左边两块,对应 v [ i − 1 ] [ j ] v[i-1][j] v[i1][j], S 2 S_2 S2 对应下边两块,对应 v [ i ] [ j − 1 ] v[i][j-1] v[i][j1], S 3 S_3 S3 对应左下那块,对应 v [ i − 1 ] [ j − 1 ] v[i-1][j-1] v[i1][j1], 则 S + = S 1 + S 2 − S 3 S += S_1 + S_2 - S_3 S+=S1+S2S3,对应于上述求前缀和转移式。   而对于ans: a n s = m a x ( a n s , d p [ i ] [ j ] + v [ i ] [ j ] ∗ ( m − i − j ) )     ( i + j ≥ m ) ans = max(ans, dp[i][j] + v[i][j] * (m - i - j) ) \ \ \ (i + j ≥ m) ans=max(ans,dp[i][j]+v[i][j](mij))   (i+jm)   而这一题 m m m 的范围是很大的,通过直接枚举天数进行dp肯定会超时。所以这时候可以想到离散化。

      比如当有3个奖励: ( 100 , 100 , 1 ) , ( 20 , 30 , 2 ) , ( 30 , 20 , 3 ) (100, 100, 1), (20, 30, 2), (30, 20, 3) (100,100,1),(20,30,2),(30,20,3),对于 d p [ 100 ] [ 100 ] dp[100][100] dp[100][100], 只需要考虑 d p [ 20 ] [ 30 ] , d p [ 30 ] [ 20 ] , d p [ 30 , 30 ] dp[20][30], dp[30][20], dp[30, 30] dp[20][30],dp[30][20],dp[30,30] 这些值怎么变化到 d p [ 100 ] [ 100 ] dp[100][100] dp[100][100] 就好了,而无需从 d p [ 100 ] [ 99 ] , d p [ 99 ] [ 100 ] dp[100][99], dp[99][100] dp[100][99],dp[99][100] 这些值得到,因此我们只需要考虑这些奖励的 x i , y i x_i, y_i xi,yi 所组成的结点就好了。为此我们需要将 x i , y i x_i, y_i xi,yi 离散化:

    scanf("%d %d", &n, &m); for(int i = 1; i <= n; i++) { scanf("%d %d %d", &pro[i].x, &pro[i].y, &pro[i].z); X[i] = pro[i].x; Y[i] = pro[i].y; } //先排序 sort(X + 1, X + 1 + n); //先排序 sort(Y + 1, Y + 1 + n); //再去重,得到去重后的元素个数 cnt1 = unique(X + 1, X + 1 + n) - (X + 1); cnt2 = unique(Y + 1, Y + 1 + n) - (Y + 1); for(int i = 1; i <= n; i++) { //将值从小到大排序后,依次映射到1, 2.... int x = lower_bound(X + 1, X + 1 + cnt1, pro[i].x) - X, y = lower_bound(Y + 1, Y + 1 + cnt2, pro[i].y) - Y; v[x][y] += pro[i].z; }

      其中 X [ i ] X[i] X[i] 中的值分别映射到 1 , 2... 1, 2 ... 1,2...   经过这样的映射之后,我们只对奖励里出现过的值进行遍历,比如上面的例子从 1 1 1 遍历到 100 100 100 可以变成 1 1 1 遍历到 3 3 3 (其中 1 1 1 对应 20 20 20 2 2 2 对应 30 30 30 3 3 3 对应 100 100 100),那么原来 d p [ i ] [ j ] dp[i][j] dp[i][j] d p [ i − 1 ] [ j ] dp[i-1][j] dp[i1][j] 相差了 1 1 1 天, 现在就相差 X [ i ] − X [ i − 1 ] X[i] - X[i-1] X[i]X[i1] 天。   求 v [ i ] [ j ] v[i][j] v[i][j] 的转移方程没有变,而求 d p [ i ] [ j ] dp[i][j] dp[i][j] 的转移方程为: d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] + v [ i − 1 ] [ j ] ∗ ( X [ i ] − X [ i − 1 ] − 1 ) , d p [ i ] [ j − 1 ] + v [ i ] [ j − 1 ] ∗ ( Y [ j ] − Y [ j − 1 ] − 1 ) ) + v [ i ] [ j ] dp[i][j] = max(dp[i-1][j] + v[i-1][j] * (X[i] - X[i-1] - 1), dp[i][j-1] + v[i][j-1] * (Y[j] - Y[j-1] - 1)) + v[i][j] dp[i][j]=max(dp[i1][j]+v[i1][j](X[i]X[i1]1),dp[i][j1]+v[i][j1](Y[j]Y[j1]1))+v[i][j]   对于每一个 d p [ i ] [ j ] dp[i][j] dp[i][j],若 i + j ≤ m i + j ≤ m i+jm, 那么: a n s = m a x ( a n s , d p [ i ] [ j ] + ( m − X [ i ] − Y [ j ] ) ∗ v [ i ] [ j ] ) ans = max(ans, dp[i][j] + (m - X[i] - Y[j]) * v[i][j]) ans=max(ans,dp[i][j]+(mX[i]Y[j])v[i][j])

      这样得到的即为需要的最大值,其中离散化操作为 O ( n l o g n ) O(nlogn) O(nlogn), 求前缀和,dp 为 O ( n 2 ) O(n^2) O(n2)

      

    代码

    #include <bits/stdc++.h> using namespace std; typedef unsigned long long ll; const int maxn = 1005; const int INF = 0x3f3f3f3f; const ll mod = 998244353; struct node { int x, y, z; }pro[maxn]; int n, m, X[maxn], Y[maxn], cnt1, cnt2; ll dp[maxn][maxn], v[maxn][maxn], ans; int main() { scanf("%d %d", &n, &m); //进行离散化操作 for(int i = 1; i <= n; i++) { scanf("%d %d %d", &pro[i].x, &pro[i].y, &pro[i].z); X[i] = pro[i].x; Y[i] = pro[i].y; } sort(X + 1, X + 1 + n); //排序 sort(Y + 1, Y + 1 + n); cnt1 = unique(X + 1, X + 1 + n) - (X + 1); //去重 cnt2 = unique(Y + 1, Y + 1 + n) - (Y + 1); for(int i = 1; i <= n; i++) { ///将值从小到大排序后,依次映射到1, 2.... int x = lower_bound(X + 1, X + 1 + cnt1, pro[i].x) - X, y = lower_bound(Y + 1, Y + 1 + cnt2, pro[i].y) - Y; v[x][y] += pro[i].z; } for(int i = 1; i <= cnt1; i++) //求前缀和 for(int j = 1; j <= cnt2; j++) v[i][j] += v[i-1][j] + v[i][j-1] - v[i-1][j-1]; for(int i = 1; i <= cnt1; i++) { for(int j = 1; j <= cnt2; j++) //进行dp { dp[i][j] = max(dp[i-1][j] + v[i-1][j] * (X[i] - X[i-1] - 1), dp[i][j-1] + v[i][j-1] * (Y[j] - Y[j-1] - 1)) + v[i][j]; if(X[i] + Y[j] <= m) //这个if判断很关键! ans = max(ans, dp[i][j] + (m - X[i] - Y[j]) * v[i][j]); } } printf("%lld\n", ans); }

      

    一点收获

    1.离散化的三步骤:①排序; ②去重; ③二分查找用下标对应值 2.二维求前缀和的方法

    Processed: 0.018, SQL: 9