[离散化dp] NC19809 growth

题面

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

分析

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

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

	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] 中的值分别映射到 1,2...1, 2 ...
  经过这样的映射之后,我们只对奖励里出现过的值进行遍历,比如上面的例子从 11 遍历到 100100 可以变成 11 遍历到 33 (其中 11 对应 202022 对应 303033 对应 100100),那么原来 dp[i][j]dp[i][j]dp[i1][j]dp[i-1][j] 相差了 11 天, 现在就相差 X[i]X[i1]X[i] - X[i-1] 天。
  求 v[i][j]v[i][j] 的转移方程没有变,而求 dp[i][j]dp[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]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]dp[i][j],若 i+jmi + j ≤ m, 那么:
ans=max(ans,dp[i][j]+(mX[i]Y[j])v[i][j])ans = max(ans, dp[i][j] + (m - X[i] - Y[j]) * v[i][j])

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

  

代码

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

展开阅读全文

Python数据分析与挖掘

01-08
92讲视频课+16大项目实战+源码+¥800元课程礼包+讲师社群1V1答疑+社群闭门分享会=99元   为什么学习数据分析?       人工智能、大数据时代有什么技能是可以运用在各种行业的?数据分析就是。       从海量数据中获得别人看不见的信息,创业者可以通过数据分析来优产品,营销人员可以通过数据分析改进营销策略,产品经理可以通过数据分析洞察用户习惯,金融从业者可以通过数据分析规避投资风险,程序员可以通过数据分析进一步挖掘出数据价值,它和编程一样,本质上也是一个工具,通过数据来对现实事物进行分析和识别的能力。不管你从事什么行业,掌握了数据分析能力,往往在其岗位上更有竞争力。    本课程共包含五大模块: 一、先导篇: 通过分析数据分析师的一天,让学员了解全面了解成为一个数据分析师的所有必修功法,对数据分析师不在迷惑。   二、基础篇: 围绕Python基础语法介绍、数据预处理、数据可视以及数据分析与挖掘......这些核心技能模块展开,帮助你快速而全面的掌握和了解成为一个数据分析师的所有必修功法。   三、数据采集篇: 通过网络爬虫实战解决数据分析的必经之路:数据从何来的问题,讲解常见的爬虫套路并利用三大实战帮助学员扎实数据采集能力,避免没有数据可分析的尴尬。   四、分析工具篇: 讲解数据分析避不开的科学计算库Numpy、数据分析工具Pandas及常见可视工具Matplotlib。   五、算法篇: 算法是数据分析的精华,课程精选10大算法,包括分类、聚类、预测3大类型,每个算法都从原理和案例两个角度学习,让你不仅能用起来,了解原理,还能知道为什么这么做。
©️2020 CSDN 皮肤主题: 1024 设计师: 上身试试 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值