题目描述

有NN棵松果树从左往右排一行,桃桃是一只松鼠,它现在在第一棵松果树上。它想吃尽量多的松果,但它不想在地上走,而只想从一棵树跳到另一棵树上。松鼠的体力有个上限,每次不能跳的太远,也不能跳太多次。每当它跳到一棵树上,就会把那棵树上的松果全部都吃了。它最多能吃到多少个松果?

输入格式 1725.in

第一行,三个整数:NN、DD、MM。NN表示松果树的数量,DD表示松鼠每次跳跃的最大距离,MM表示松鼠最多能跳跃M次。
接下来有NN行,每行两个整数:AiA_i和BiB_i。其中AiA_i表示第ii棵树上的松果的数量,BiB_i表示第ii棵树与第1棵树的距离,其中B1B_1保证是0。
数据保证这NN棵树从左往右的次序给出,即BiB_i是递增的,不存在多棵树在同一地点。

输出格式 1725.out

一个整数。

输入样例 1725.in

5 5 2
6 0
8 3
4 5
6 7
9 10

输出样例 1725.out

20

数据范围

Ai≤10000Ai \leq 10000, D≤10000D \leq 10000
对于40%的数据,M<N≤100M , Bi≤10000Bi \leq 10000
对于100%的数据,M<N≤2000M , Bi≤10000Bi \leq 10000


初看,这题 dp 的模型是非常明显的。如果用 f[i][j]f[i][j] 表示前 ii 棵树跳 jj 次的最多松果数,很容易就可以得到

f[i][j]=max(f[k][j−1])+A[i](B[i]−B[k]<D)

f[i][j]=max(f[k][j-1])+A[i]\qquad(B[i]-B[k]

这种做法的正确性也是显然的,但是复杂度太高了,达到 O(n3)O(n^3) 的级别,只能拿 40 分。

分析一下数据范围,其实 O(n2)O(n^2) 的复杂度还是可以承受的,所以优化算法的关键就在于如何快速地找出符合条件的 f[k][j−1]f[k][j-1] 的最大值。我们知道,朴素地找肯定是行不通的,那么只能用一个数据结构进行维护。

传统的维护最值数据结构在此题中均表现不佳,如线段树所需空间太大,树状数组无法满足位置范围限制的需求。但还有一种轻巧易用的数据结构,那就是单调队列

不妨维护这样一个队列:总是保证队列中的元素值单调递减,且队中元素的最小下标与最大下标差总是在 dd 以内。但是要维护的值是基于跳的次数的不同而受到影响的,因此要维护的是一个二维的单调队列。也就是说,当计算一个新的值 f[i][j]f[i][j] 的时候,就取 j−1j-1 的单调队列队首元素值;计算完毕后更新一遍队列,就更新 jj 的单调队列。

参考代码:

#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <queue>using namespace std;const int maxn = 2e3 + 10;
const int maxm = 2e3 + 10;struct Tnode {int pos, val;Tnode () : pos(0), val(0) {}Tnode (int x, int y) : pos(x), val(y) {}
};int n, d, m;
int a[maxn], b[maxn];
int dp[maxn][maxm];int head[maxm], tail[maxm];
Tnode q[maxn][maxm];void debug_output() {for (int i = 0; i <= m; i++) {for (int j = head[i]; j < tail[i]; j++) printf("%d %d|", q[i][j].pos, q[i][j].val);putchar('\n');}puts("=============");
}int main(void) {freopen("1725.in", "r", stdin);freopen("1725.out", "w", stdout);scanf("%d%d%d", &n, &d, &m);for (int i = 0; i < n; i++) scanf("%d%d", &a[i], &b[i]);dp[0][0] = a[0];q[0][tail[0]++] = Tnode(0, a[0]);for (int i = 1; i < n; i++) {for (int j = 1; j <= min(i, m); j++) {while (head[j - 1] < tail[j - 1] && b[i] - q[j - 1][head[j - 1]].pos > d) head[j - 1]++; //将与当前位置距离超过 d 的元素出队dp[i][j] = q[j - 1][head[j - 1]].val + a[i]; //队首值即为跳 j - 1 次且与当前位置距离在 d 以内的最大值}for (int j = 1; j <= min(i, m); j++) { //计算完后统一维护队列while (head[j] < tail[j] && q[j][tail[j] - 1].val < dp[i][j]) tail[j]--;q[j][tail[j]++] = Tnode(b[i], dp[i][j]);//printf("i=%d j=%d\n", i, j); debug_output();}}int ans = 0;for (int i = 0; i < n; i++) ans = max(ans, dp[i][m]);printf("%d\n", ans);return 0;
}

关于利用单调性优化 dp 的问题,JSOI 2009 有一篇相关的论文讲得很棒,如果有兴趣可以自行查阅。

2017/8/5 update
这里还存在一个细节问题需要注意:如果计算完立即维护单调队列,且 jj 按照一般的从小到大的计算顺序,就会出现问题。

不难理解:在计算 f[i][j]f[i][j] 的过程中取 f[k][j−1]f[k][j-1] 的时候,显然合法的 kk 应该满足 k<ik。但是计算完马上更新队列就有可能导致接下来取了 f[i][j−1]f[i][j-1] 的值,也就是“从当前跳到当前”的情况。

有两种解决方法可以避免出现这样的错误:一是参考 01 背包的一维空间做法,jj 从大到小求解;二是对于同一个 ii,统一求解完所有的 f[i][j]f[i][j] 后再去维护单调队列。

另:附线段树版本代码
注意下面的代码中用了一个小优化,即先统一二分预处理上一步的最远可能位置,并记为 lastilast_i。

#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>using namespace std;const int MAXN = 2e3 + 10;struct Tnode {int val;int left_child, right_child;
} tree[MAXN * MAXN << 1];
int freepoint;struct SegmentTree {int root;SegmentTree () : root(0) {} int get_point(int l, int r) {tree[freepoint] = (Tnode){0, -1, -1};return freepoint++;}int create(int l, int r) {int p = get_point(l, r);if (l < r) {int mid = l + r >> 1;tree[p].left_child = create(l, mid);tree[p].right_child = create(mid + 1, r);}return p;}void push_up(int cur) {tree[cur].val = max(tree[tree[cur].left_child].val, tree[tree[cur].right_child].val);}void update_one(int cur, int l, int r, int pos, int v) {Tnode& nod = tree[cur];if (l == pos && r == pos) {nod.val = v;return;}int mid = l + r >> 1;if (pos <= mid) update_one(nod.left_child, l, mid, pos, v);else update_one(nod.right_child, mid + 1, r, pos, v);push_up(cur);}int query(int cur, int ll, int rr, int l, int r) {Tnode& nod = tree[cur];if (cur == -1 || r < ll || rr < l) return 0;
//      printf("%d %d %d %d %d\n", cur, ll, rr, l, r);if (l <= ll && rr <= r) return nod.val;int mid = ll + rr >> 1;return max(query(nod.left_child, ll, mid, l, r), query(nod.right_child, mid + 1, rr, l, r));}
} SegT[MAXN];int N, D, M;
int A[MAXN], B[MAXN], last[MAXN], f[MAXN][MAXN];int main(void) {freopen("1725.in", "r", stdin);freopen("1725.out", "w", stdout);scanf("%d%d%d", &N, &D, &M);for (int i = 1; i <= N; i++) scanf("%d%d", &A[i], &B[i]);for (int i = 2; i <= N; i++) last[i] = lower_bound(B + 1, B + i, B[i] - D) - B;for (int i = 0; i <= M; i++) SegT[i].root = SegT[i].create(1, N);int ans;ans = f[1][0] = A[1]; SegT[0].update_one(SegT[0].root, 1, N, 1, A[1]);for (int i = 2; i <= N; i++)for (int j = min(i - 1, M); j; j--) {f[i][j] = SegT[j - 1].query(SegT[j - 1].root, 1, N, last[i], i - 1) + A[i];ans = max(ans, f[i][j]);SegT[j].update_one(SegT[j].root, 1, N, i, f[i][j]);}printf("%d\n", ans);return 0;
}


[SMOJ1725]松果相关推荐

  1. 松果出行CTO朱蓝天做客《智慧Talk》,解开共享电单车产研密码

    松果出行CTO朱蓝天(右)接受腾讯新闻<智慧Talk>采访 文 | 徐叁 来源 | 螳螂观察 近两年,越来越多的共享电单车出现在城市的大街小巷.这些色彩绚丽.使用便捷的车辆,与家庭自购电单 ...

  2. 新华社报道关注松果出行:助力国家碳达峰碳中和目标

    国家通讯社新华社14日刊发题为<碳达峰.碳中和怎么干?科技创新这样来助力>的新闻报道,关注科技创新助力碳达峰.碳中和主题,松果出行作为典型案例被给予重点关注.报道认为,松果出行立足自身产品 ...

  3. 下沉市场惊现出行小巨头 松果共享电单车日订单破300w

    (图片来源于网络) 文|陈小江 来源 | 螳螂财经(ID:TanglangFin) 不久前,共享电单车坟场的出现,告诉我们从单一的用户视角出发,可能撑不起共享电单车的天花板. 1月29日,在极客公园I ...

  4. CCF-百度松果基金闭门研讨会成功举办,百度飞桨提供基金平台支持

    2020年10月23日下午,首届CCF-百度松果基金(以下简称"松果基金")闭门研讨交流会在京举行,作为2020年中国计算机大会的分论坛之一,研讨会受到了业内广泛关注.在会上,20 ...

  5. 2020年度“CCF-百度松果基金”评审结果公示

    祝贺23名青年学者入选2020 CCF-百度松果基金. CCF-百度松果基金(以下简称"松果基金")于2020年由CCF和百度公司共同发起,为海内外高校及科研院所的青年学者搭建产学 ...

  6. CCF-百度松果基金正式“亮出”申报细则,加码产学研前沿合作

    7月27日,百度与中国计算机学会(以下简称CCF)联合成立的"CCF-百度松果基金"(以下简称松果基金)在京举行签约仪式,双方联手面向海内外高校及科研院所的青年学者,搭建产学研合作 ...

  7. 为全力发展AIOT,小米把松果电子分拆重组了

    雷锋网消息,今天下午小米集团组织部发布组织架构调整邮件,披露为了配合公司AIoT战略加速落地,推动芯片研发业务更快发展,小米旗下的全资子公司松果电子团队进行重组,部分团队分拆组建新公司南京大鱼科技. ...

  8. 松鼠分松果解题 c++

    松鼠分松果解题 c++ 题目 描述 在一个天气晴朗的早晨,有k只可爱的小松鼠来到文山湖旁,看见地上有n堆松果 松鼠王国有一个规定,看到有很多堆松果时,取松果的堆数必须连续.比如只能取第2堆,第3堆,第 ...

  9. 【smoj 1167】松果

    题目描述 大森林有熊兄弟的好朋友松鼠蹦蹦,一天蹦蹦来到一条很长的小路,发现沿路地上都有松果,高兴极了,决定尽可能多吃松果. 蹦蹦观察到,每个松果的重量并不一定相同,可蹦蹦的肚子容量有限,总共最多只能吃 ...

最新文章

  1. 双 11 的狂欢,干了这碗「流量防控」汤
  2. expdp oracle 并行_关于Expdp/Impdp 并行导入导出详细测试结果和并行参数的正确理解!!...
  3. 你看我还有机会吗?这么GAN让我秃然荒了!
  4. 你能分清多进程与多线程吗?
  5. 简练软考知识点整理-控制范围
  6. 计算机网络中缓存技术,编程达人
  7. 苹果降价潮一波接一波 两款廉价iPad“箭在弦上”
  8. 分治-寻找第k小的数
  9. spring中的context:include-filter和context:exclude-filter的区别
  10. 使用Seam Framework + JBoss 5.0 开发第一个Web应用 - 简单投票程序
  11. QT项目六:简易客户信息管理系统
  12. 小米手机运行linux,在Linux系统下使用小米刷机工具:XiaoMiTool V2(XMT2)
  13. 计算机d盘给c盘,win10电脑D盘合并分区到c盘的两种方法
  14. arcgis注册数据源_将表注册到地理数据库
  15. 计算机出现蓝屏怎么恢复,电脑蓝屏怎么解决,小编教你如何恢复正常
  16. 如何把控单元测试的粒度让你省时省力甚至一劳永逸建议一键收藏
  17. Linux中rm -rf 文件夹,删不掉
  18. Android4种网络连接方式HttpClient、HttpURLConnection、OKHttp和Volley优缺点和性能对比
  19. java tapestry_Java Web 框架 Tapestry
  20. 程序员的岗位路线规划,不止是编程?

热门文章

  1. java中块的作用是什么意思_java中finally块儿是怎么工作的?有什么意义?
  2. wundos无法对计算机进行启动,fanhuiwundos电脑提醒不是正版想
  3. mysql的in和exist的区别
  4. while(1) 什么意思 while(i--)什么意思?
  5. python写spark_Spark2.1.0+入门:文件数据读写(Python版)
  6. 语音识别—前端录音传给后台语音识别
  7. replaceAll() 方法
  8. 北京尚学堂JAVA开发学习 MYSQL笔记
  9. layui联动选择框
  10. 零基础创作专业wordpress网站06-制作主页(elementor)