当前位置: 首页 > news >正文

Luogu P3287 [SCOI2014] 方伯伯的玉米田 题解 [ 紫 ] [ 多维 DP ] [ 贪心 ] [ 树状数组 ] [ 状态设计优化 ]

[SCOI2014] 方伯伯的玉米田:单 \(\log\) 做法比较 educational。

首先考虑一个暴力 DP:\(dp_{i, j, k}\) 表示以 \(i\) 的元素结尾,用了 \(j\) 个区间加,LIS 的最后一个元素为 \(k\) 的 LIS 长度。

进一步观察性质,发现每次区间加的右端点为 \(n\) 一定是最优的。用调整法容易证明,如果一个区间加的右端点没有延伸到 \(n\),那么可能在未被区间加的地方就不能继承前面的 LIS 了。

因为区间加会一直延伸到 \(n\),所以有式子:\(k = a_i + j\),DP 状态就被我们省掉了一维。

然后转移是平凡的,式子如下:

\[dp_{i,j}\gets \max_{k < i, l \le j, a_k + l \le a_i + j} dp_{k, l} + 1 \]

注意到这是个三维偏序的转移,所以直接上 CDQ 优化大概就能过了。但是我们转移的时候已经是按顺序转了 \(i\) 的,因此只要考虑后面的二维偏序即可。而又因为后面两维值域较小,所以直接用二维树状数组维护即可。时间复杂度 \(O(nk\log n\log k)\)

考虑如何优化掉一个 \(\log\)。注意到这三维都是根据其前缀的信息转移,所以考虑对前缀转移的一个常见 trick:状态设计优化为存储其前缀的所有信息。在本题中,就体现为 \(dp_{i,j, k}\) 表示对于 \(i\) 位,用了不超过 \(j\) 个区间加,LIS 的最后一个元素不超过 \(k\) 的 LIS 长度。因为 \(k\) 的等式在此时不成立了,所以不能省略 \(k\) 的一维。

用了前缀优化状态后,转移显然从 \(dp_{i-1, j-1,k},dp_{i-1, j, k-1}\) 得到。考虑优化最后一维,发现在前两维确定后,如果 \(i\) 要成为 LIS 中的元素,那么可以直接从前面转移了的 DP 值中选择 \(k \le a_i + j\) 的 DP 值;否则直接从前面的继承过来就行。因此我们可以把 \(k\) 这一维放在树状数组上,只有在 \(i\) 为 LIS 元素的时候才从树状数组上转移,否则直接继承前面的状态即可。

对于省略 \(k\) 可以这么理解:在第二种情况中 \(k\) 其实是不发生任何改变的,而只有第一种情况中 \(k\) 才会对转移产生影响。省略 \(k\) 相当于把原来的 DP 数组拆成了两个,一个是第一种情况的 DP 值,一个是第二种情况的 DP 值。因为第一种情况也只会从第一种情况转移,所以第一种情况内部使用树状数组存 \(k\) 那一维的 DP 值即可。

时间复杂度 \(O(nk\log (a_i + k))\)

#include <bits/stdc++.h>
#define fi first
#define se second
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
#define lc(x) (tr[x].ls)
#define rc(x) (tr[x].rs)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
using pi = pair<int, int>;
const int N = 10005, K = 505;
int n, m, dp[N][K], a[N];
int lowbit(int x)
{return (x & (-x));
}
struct BIT{int tr[N];void update(int p, int v){p++;while(p < N){tr[p] = max(tr[p], v);p += lowbit(p);}}int query(int p){int res = 0;p++;while(p){res = max(res, tr[p]);p -= lowbit(p);}return res;}
}tra[N], trb[N];
int main()
{// freopen("P3287.in", "r", stdin);// freopen("P3287.out", "w", stdout);ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin >> n >> m;for(int i = 1; i <= n; i++) cin >> a[i];for(int i = 1; i <= n; i++){for(int j = m; j >= 0; j--){dp[i][j] = max(tra[j].query(a[i] + j), trb[a[i] + j].query(j)) + 1;tra[j].update(a[i] + j, dp[i][j]);trb[a[i] + j].update(j, dp[i][j]);}for(int j = 0; j <= m; j++)dp[i][j] = max(dp[i - 1][j], dp[i][j]);}cout << dp[n][m];return 0;
}
http://www.sczhlp.com/news/10538/

相关文章:

  • VSCode添加到右键菜单中
  • css 红包打开静态效果
  • 厂商官网
  • Java基础学习的一些小细节
  • 2025.8.12 java课堂笔记
  • 记录---高效前端开发:使用 unplugin-auto-import 实现依赖自动导入
  • 【IT转码 Day02】
  • 锐捷
  • 思科
  • 华三
  • 竞速之渊
  • 注册 JVM 关闭钩子(Shutdown Hook)的方法
  • 2025.7.28 CSP-S模拟赛28
  • 服务器如何配置防火墙管理端口访问?
  • 【做题记录】数论(马思博)
  • 渗透测试十年回忆录:从漏洞扫描到社会工程的艺术
  • xx-准备工作
  • 月份选择每个月不能重复
  • 基于MATLAB实现的随机森林算法对共享单车签入签出数量进行预测
  • 8 月考试
  • .net MVC4中提示Newtonsoft.Json, Version=4.5.0.0
  • MySQL 并发控制和日志
  • 基于幅度的和差测角程序
  • ZR 25 summer D7T1 题解 | 树上问题,dp
  • EditText如何设置
  • 关于 git reset --hard 引发的代码故障(附故障原因及解决方案)
  • 【典型案例】利用高光谱遥感技术进行稀有矿产勘探 - ENVI
  • 学 STM32 第一步:入门工具怎么选?避免新手常见误区
  • Flutter 布局控件使用详解 - 指南
  • LHA6958D是ADS8588的代替料