讲课讲得非常清楚啊,我绝赞膜拜。节奏可以,思路清晰,解法自然,为讲师点赞。

第一个题是 loj3282 / joisc2020 - Treatment Project。原问题由 \(\left(S, t, w\right)\) 三个维度构成,分别表示村民被治疗的状态,时间,花费。比较自然的思路是针对 \(t\) 做规划,但是这样有一个问题——\(t\) 和 \(S\) 是息息相关的,若从 \(t\) 入手则几乎不得不记录 \(S\),这是不被接受的。考虑直接 \(S\) 入手。如果我可以考虑一段连续的区间而不是子序列,是不是复杂度一定会降?那么考察前缀,具体,设 \(f_i\) 表示将 \([1, i]\) 的村民治好的最小花费。进一步地,不妨将 \(f_i\) 重新描述为将 \([1, r_i]\) 的村民治好的最小花费。

为什么这样设计状态就不需要考虑时间的影响了(不是说整体,仅仅是在设计状态的时候)?我个人的理解是这样做将村民,或者说 \(S\),作为第一个研究的对象,而时间则作为后续求解中对 \(f\) 的限制,所以放到后面考虑即可。

转移方程即为 \(\displaystyle f_i\gets\min_{1\leqslant j<i,r_j-l_i+1\geqslant|t_i-t_j|}\{f_j\}+w_i\)。将绝对值拆开,这里以 \(t_i\geqslant t_j\) 为例。那么转移的条件就成了 \(r_j+t_j+1\geqslant t_i+l_i\)。那么现在转移有二维偏序的关系,即 \(t_i\geqslant t_j,r_j+t_j+1\geqslant t_i+l_i\)。在优化之前,首先注意到这是一个最短路模型,转移条件即连边的条件,考虑用最优的 \(f_j\) 去松弛 \(f_i\),那么每个 \(f_i\) 就只会被松弛一次(几乎是自明的),于是按 \(t\) 排序后用线段树维护即可。

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. // implementation: time-ordered
  4. using ll = long long;
  5. using pli = pair<ll, int>;
  6. const ll inf = 1e18;
  7. int n, m;
  8. ll dp[100100];
  9. struct node {
  10. int l, r, t;
  11. ll w;
  12. } a[100100];
  13. priority_queue<pli, vector<pli>, greater<pli>> q; // slacked
  14. struct segment_tree {
  15. ll mix[400100], miy[400100];
  16. void pull(int now) {
  17. mix[now] = min(mix[now*2], mix[now*2+1]);
  18. miy[now] = min(miy[now*2], miy[now*2+1]);
  19. }
  20. void ins(int pos, ll x, ll y, int now=1, int l=1, int r=n) {
  21. if (l == r) {
  22. mix[now] = x, miy[now] = y;
  23. return;
  24. }
  25. int mid = (l+r)/2;
  26. if (mid >= pos) ins(pos, x, y, now*2, l, mid);
  27. else ins(pos, x, y, now*2+1, mid+1, r);
  28. pull(now);
  29. }
  30. void slackx(int lq, int rq, ll lim, ll dlt, int now=1, int l=1, int r=n) {
  31. if (lq > rq || l > rq || r < lq || mix[now] > lim) return;
  32. if (l == r) {
  33. dp[l] = dlt+a[l].w;
  34. q.emplace(dp[l], l);
  35. mix[now] = miy[now] = inf;
  36. return;
  37. }
  38. int mid = (l+r)/2;
  39. slackx(lq, rq, lim, dlt, now*2, l, mid);
  40. slackx(lq, rq, lim, dlt, now*2+1, mid+1, r);
  41. pull(now);
  42. }
  43. void slacky(int lq, int rq, ll lim, ll dlt, int now=1, int l=1, int r=n) {
  44. if (lq > rq || l > rq || r < lq || miy[now] > lim) return;
  45. if (l == r) {
  46. dp[l] = dlt+a[l].w;
  47. q.emplace(dp[l], l);
  48. mix[now] = miy[now] = inf;
  49. return;
  50. }
  51. int mid = (l+r)/2;
  52. slacky(lq, rq, lim, dlt, now*2, l, mid);
  53. slacky(lq, rq, lim, dlt, now*2+1, mid+1, r);
  54. pull(now);
  55. }
  56. } sgt;
  57. signed main() {
  58. ios::sync_with_stdio(0);
  59. cin.tie(0);
  60. cin >> m >> n;
  61. for (int i=1;i<=n;++i) {
  62. dp[i] = inf;
  63. cin >> a[i].t >> a[i].l >> a[i].r >> a[i].w;
  64. }
  65. sort(a+1, a+n+1, [&](node a, node b) {
  66. return a.t < b.t;
  67. });
  68. for (int i=1;i<=n;++i) {
  69. if (a[i].l == 1) {
  70. dp[i] = a[i].w;
  71. q.emplace(dp[i], i);
  72. sgt.ins(i, inf, inf);
  73. }
  74. else {
  75. sgt.ins(i, a[i].l-a[i].t, a[i].l+a[i].t);
  76. }
  77. }
  78. while (!q.empty()) {
  79. // use nodes slacked to slack other nodes
  80. int x = q.top().second;
  81. q.pop();
  82. sgt.slackx(1, x-1, a[x].r-a[x].t+1, dp[x]);
  83. sgt.slacky(x+1, n, a[x].r+a[x].t+1, dp[x]);
  84. }
  85. ll ans = inf;
  86. for (int i=1;i<=n;++i) {
  87. if (a[i].r == m) {
  88. ans = min(ans, dp[i]);
  89. }
  90. }
  91. if (ans == inf) cout << "-1\n";
  92. else cout << ans << "\n";
  93. return 0;
  94. }

第二个题是 codeforces - 1476F,整个课上唯一一个自己想出来的题,还错了个细节。直接 dp 有着自明的后效性,说两种消除之的方法。第一个是「将 \(i\) 点亮灯的能力挂到 \(i\) 能够点亮的最右边的灯上」,这样一次操作只会影响前面的,这消除了后效性。第二个直接在 dp 状态上下手,也是正解的思路,即设 \(f_i\) 表示以 \([1, i]\) 的灯能够点亮的最长前缀(完全可能超过 / 不足 \(i\)),这样相当于将「点灯的灯」和「被点的灯」隔离开了,影响就被消除了。

因为我写的第二个,所以说一下第二个(其实是差不多的)。先考虑 \(i\) 向右照的情况,转移非常简单 \(f_i\gets\max\{f_i, i+p_i\}\)(当然 \(f_i\) 要先继承 \(f_{i-1}\),这也昭示了其单调性)。\(i\) 向左照的情况比较复杂。考虑有这样一个 \(j\),满足 \(f_j\geqslant i-p_i-1\)(保证维护的是前缀),那么让 \(\forall t,s.t.j<t<i\) 向右照是最优的,贪心地想,我们就需要使得 \(j\) 最小,由于 \(f\) 有单调性,直接二分即可,然后 RMQ 找 \((j, i)\) 区间中最大的 \(t+p_t\) 即可。

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. int n, p[300100], dp[300100], dp2[20][300100], lgs[300100], pre[300100], isl[300100];
  4. int get(int l, int r) {
  5. if (l > r) return 0;
  6. int k = lgs[r-l+1];
  7. return max(dp2[k][l], dp2[k][r-(1<<k)+1]);
  8. }
  9. void print(int now) {
  10. if (now == 0) return;
  11. print(pre[now]);
  12. if (isl[now]) {
  13. cout << "R";
  14. return;
  15. }
  16. for (int i=pre[now]+1; i<now; ++i) cout << "R";
  17. cout << "L";
  18. }
  19. void solve() {
  20. cin >> n;
  21. for (int i=1;i<=n;++i) {
  22. cin >> p[i];
  23. dp2[0][i] = i+p[i];
  24. }
  25. for (int i=1;(1<<i)<=n;++i) {
  26. for (int j=1;j+(1<<i)-1<=n;++j) {
  27. dp2[i][j] = max(dp2[i-1][j], dp2[i-1][j+(1<<(i-1))]);
  28. }
  29. }
  30. pre[1] = 0, isl[1] = 1;
  31. for (int i=2;i<=n;++i) {
  32. dp[i] = dp[i-1], pre[i] = i-1, isl[i] = 1;
  33. if (dp[i-1] >= i) {
  34. dp[i] = max(dp[i], i+p[i]);
  35. }
  36. int l = 0, r = i-1, j = -1, mid;
  37. while (l <= r) {
  38. mid = (l+r)/2;
  39. if (dp[mid] >= i-p[i]-1) r = mid-1, j = mid;
  40. else l = mid+1;
  41. }
  42. if (j == -1) continue;
  43. if (max(i-1, get(j+1, i-1)) >= dp[i]) {
  44. isl[i] = 0, pre[i] = j;
  45. }
  46. dp[i] = max({dp[i], i-1, get(j+1, i-1)});
  47. }
  48. if (dp[n] >= n) {
  49. cout << "YES\n";
  50. print(n);
  51. cout << "\n";
  52. return;
  53. }
  54. cout << "NO\n";
  55. }
  56. signed main() {
  57. ios::sync_with_stdio(0);
  58. cin.tie(0);
  59. int tt;
  60. for (int i=2; i<300100; ++i) {
  61. lgs[i] = lgs[i>>1]+1;
  62. }
  63. for (cin>>tt;tt--;) {
  64. solve();
  65. }
  66. }

第三个题是 codeforces - 1523F。再次考虑这个题的维度 \((S, t, i, w)\),分别表示点亮传送塔的集合,时间,所处位置(传送塔或任务点),完成任务数量。据此可以写出一个朴素的 dp,\(f_{S, i, w}\) 表示点亮传送塔的集合为 \(S\),当前在位置 \(i\),完成了 \(w\) 个任务,所花费的最小时间。这里把题目中的「答案」完成任务数量作为状态并非不自然,是单纯因为时间太大忍不下。这里有两个重要的观察:

  • 处在传送塔时,不关心自己的位置;
  • 处在任务点时,不关心当前的时间。

正确性不必多言,关键在于观察到之的洞察力。于是我们可以优化状态(这里的根据是,传送塔 \(\cup\) 任务点 \(=\) 所有地址),分别定义 \(f_{S, i}\),\(g_{S, i}\) 表示点亮传送塔的集合为 \(S\),完成了 \(i\) 个任务,并且现在处于传送塔的最小时间,和点亮传送塔的集合为 \(S\),在 \(i\) 个地址,最大完成任务总数。转移分 传送塔 \(\rightarrow\) 任务点、传送塔 \(\rightarrow\) 传送塔、任务点 \(\rightarrow\) 任务点、任务点 \(\rightarrow\) 传送塔 讨论即可。同时还有转移顺序的问题,具体见代码。

  1. #include <bits/stdc++.h>
  2. #define cmin(x, y) x = min(x, y)
  3. #define cmax(x, y) x = max(x, y)
  4. using namespace std;
  5. const int inf = 0x3f3f3f3f;
  6. int n, m, ans = -inf;
  7. int w[16484][120], up, f[16484][120], g[16484][120];
  8. struct pnt {
  9. int x, y, t;
  10. } a[120];
  11. int dst(int i, int j) {
  12. return abs(a[i].x-a[j].x)+abs(a[i].y-a[j].y);
  13. }
  14. signed main() {
  15. ios::sync_with_stdio(0);
  16. cin.tie(0);
  17. cin >> n >> m;
  18. up = 1<<n;
  19. for (int i=0; i<n; ++i) {
  20. cin >> a[i].x >> a[i].y;
  21. }
  22. for (int i=n; i<n+m; ++i) {
  23. cin >> a[i].x >> a[i].y >> a[i].t;
  24. }
  25. sort(a+n, a+n+m, [&](pnt x, pnt y) {
  26. return x.t < y.t;
  27. });
  28. for (int s=0; s<up; ++s) {
  29. for (int i=0; i<n+m; ++i) {
  30. w[s][i] = inf;
  31. for (int j=0; j<n; ++j) {
  32. if (s&(1<<j)) cmin(w[s][i], dst(i, j));
  33. }
  34. }
  35. }
  36. for (int s=0; s<up; ++s) {
  37. for (int i=0; i<m; ++i) f[s][i] = inf, g[s][i] = -inf;
  38. f[s][m] = inf;
  39. }
  40. for (int i=0; i<n; ++i) f[1<<i][0] = 0;
  41. for (int i=0; i<m; ++i) g[0][i] = 1;
  42. for (int s=0; s<up; ++s) {
  43. for (int i=0; i<=m; ++i) {
  44. if (f[s][i] != inf) {
  45. for (int j=0; j<n; ++j) {
  46. if ((s&(1<<j)) == 0) {
  47. cmin(f[s|(1<<j)][i], f[s][i]+w[s][j]);
  48. }
  49. }
  50. for (int j=0; j<m; ++j) {
  51. if (f[s][i]+w[s][j+n] <= a[j+n].t) {
  52. cmax(g[s][j], i+1);
  53. }
  54. }
  55. }
  56. }
  57. for (int i=0; i<m; ++i) {
  58. if (g[s][i] >= 0) {
  59. for (int j=0; j<n; ++j) {
  60. if ((s&(1<<j)) == 0) {
  61. cmin(f[s|(1<<j)][g[s][i]], min(dst(i+n, j), w[s][j])+a[i+n].t);
  62. }
  63. }
  64. for (int j=i+1; j<m; ++j) {
  65. if (min(dst(i+n, j+n), w[s][j+n])+a[i+n].t <= a[j+n].t) {
  66. cmax(g[s][j], g[s][i]+1);
  67. }
  68. }
  69. cmax(ans, g[s][i]);
  70. }
  71. }
  72. }
  73. cout << ans << "\n";
  74. return 0;
  75. }

zxy 简单 dp 大讲堂的更多相关文章

  1. HDU 1087 简单dp,求递增子序列使和最大

    Super Jumping! Jumping! Jumping! Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 ...

  2. Codeforces Round #260 (Div. 1) A. Boredom (简单dp)

    题目链接:http://codeforces.com/problemset/problem/455/A 给你n个数,要是其中取一个大小为x的数,那x+1和x-1都不能取了,问你最后取完最大的和是多少. ...

  3. codeforces Gym 100500H A. Potion of Immortality 简单DP

    Problem H. ICPC QuestTime Limit: 20 Sec Memory Limit: 256 MB 题目连接 http://codeforces.com/gym/100500/a ...

  4. 简单dp --- HDU1248寒冰王座

    题目链接 这道题也是简单dp里面的一种经典类型,递推式就是dp[i] = min(dp[i-150], dp[i-200], dp[i-350]) 代码如下: #include<iostream ...

  5. poj2385 简单DP

    J - 简单dp Crawling in process... Crawling failed Time Limit:1000MS     Memory Limit:65536KB     64bit ...

  6. hdu1087 简单DP

    I - 简单dp 例题扩展 Crawling in process... Crawling failed Time Limit:1000MS     Memory Limit:32768KB     ...

  7. poj 1157 LITTLE SHOP_简单dp

    题意:给你n种花,m个盆,花盆是有顺序的,每种花只能插一个花盘i,下一种花的只能插i<j的花盘,现在给出价值,求最大价值 简单dp #include <iostream> #incl ...

  8. hdu 2471 简单DP

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2571 简单dp, dp[n][m] +=(  dp[n-1][m],dp[n][m-1],d[i][k ...

  9. Codeforces 41D Pawn 简单dp

    题目链接:点击打开链接 给定n*m 的矩阵 常数k 以下一个n*m的矩阵,每一个位置由 0-9的一个整数表示 问: 从最后一行開始向上走到第一行使得路径上的和 % (k+1) == 0 每一个格子仅仅 ...

  10. poj1189 简单dp

    http://poj.org/problem?id=1189 Description 有一个三角形木板,竖直立放.上面钉着n(n+1)/2颗钉子,还有(n+1)个格子(当n=5时如图1).每颗钉子和周 ...

随机推荐

  1. 为teamcity的代码语法检查工具pyflakes增加支持python2和python3

    TeamCity和pyflakes TeamCity是一款由JetBrains公司开发的持续集成和部署工具,它提供了丰富的功能来帮助团队协作进行软件开发.其中包括代码检查.自动化构建.测试运行.版本控 ...

  2. 自学FHQ-treap的草稿

    更新:能过模板题(和加强版)的代码: 普通平衡树: (请自行实现读入和输出函数) 点击查看代码 #include <iostream> #include <random> #i ...

  3. ARC143

    ARC143 考试情况:一眼订正,鉴定为做出前三题. A - Three Integers 以前做过 \(n\) 个数的版本,当时还被某人嘲讽说"堆,贪心,这都做不出来?". \( ...

  4. GPT大模型下,如何实现网络自主防御

    GPT大模型下,如何实现网络自主防御 本期解读专家  李智华 华为安全AI算法专家    近年来,随着GPT大模型的出现,安全领域的攻防对抗变得更加激烈.RSAC2023人工智能安全议题重点探讨了人工 ...

  5. 六大云端 Jupyter Notebook 平台测评

    有许多方法可以与其他人共享静态 Jupyter 笔记本,例如把它发布在 GitHub 上或通过 nbviewer 链接进行分享. 但是,如果接收人已经安装了 Jupyter Notebook 环境,那 ...

  6. Vue——登录小案例、scoped、ref属性、props其他、混入mixin、插件、Element-ui

    解析Vue项目 # 1 为什么浏览器中访问某个地址,会显示某个页面组件 根组件:APP.vue 必须是 <template> <div id="app"> ...

  7. Docker 中的 .NET 异常了怎么抓 Dump

    一:背景 1. 讲故事 有很多朋友跟我说,在 Windows 上看过你文章知道了怎么抓 Crash, CPU爆高,内存暴涨 等各种Dump,为什么你没有写在 Docker 中如何抓的相关文章呢?瞧不上 ...

  8. influxdb常用sql总结

    本文为博主原创,转载请注明出处: 1.登录influxdb influx -username admin -password "password" 2.查看数据库 ##查看有哪些数 ...

  9. 【干货向】我想试试教会你如何修改Git提交信息

    Git是目前IT行业使用率最高的版本控制系统,相信大家在日常工作中也经常使用,每次Git提交都会包含提交信息,常用的包括说明.提交人和提交时间等,此篇文章主要向大家介绍下如何修改这些信息,这些命令在正 ...

  10. Windows系统使用Nginx部署Vue

    Nginx是什么? Nginx (engine x) 是一个高性能的HTTP和反向代理web服务器 ,同时也提供了IMAP/POP3/SMTP服务.Nginx是由伊戈尔·赛索耶夫为俄罗斯访问量第二的R ...