[离散化dp] NC19809 growth

题面

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.二维求前缀和的方法

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值