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

线性DP

线性DP

今日 \(2025.10.22\),距 \(CSP-J/\) \(9\)天,\(NOIP\) \(37\)

昨天把最短路爆切了,但发现自己的 \(DP\) 还是太弱了,所以我决定 \(CSP\) 剩下的这几天练练 \(DP\)。自己没系统的学过 \(DP\),所以决定从最基础的 线性 \(DP\) 开始学

在这里吐槽一句:DP黄题都比最短路的蓝题难(个人感觉)

总结一下线性DP的几种常见类型:

1.最长上升子序列

2.数塔

3.其他

最长上升子序列:

luogu B3637 最长上升子序列

板题,直接放代码:

时间复杂度:\(O(n^2)\)

#include<bits/stdc++.h>
using namespace std;
const int N=5005;
int n,a[N],f[N],ans;
int main(){cin>>n;for(int i=1;i<=n;i++) cin>>a[i];for(int i=1;i<=n;i++){f[i]=1;for(int j=1;j<i;j++){if(a[i]>a[j]) f[i]=max(f[i],f[j]+1);}ans=max(ans,f[i]);}cout<<ans;return 0;
}

还有 \(O(nlogn)\) 的贪心做法:

#include<bits/stdc++.h>
using namespace std;
const int N=5005;
int n,len,q[N];
int main(){ios::sync_with_stdio(0);cin.tie(0),cout.tie(0);cin>>n;for(int i=1;i<=n;i++){int k;cin>>k;if(len==0||k>q[len-1]) q[len++]=k;else{int x=lower_bound(q,q+len,k)-q;q[x]=k;}}cout<<len;return 0;
}

当然,还可以用权值线段树优化什么的,但本文不讨论 \(DP\) 的优化,以上这个 \(O(nlogn)\) 的贪心在后文直接使用了(有更优的复杂度当然要学)

luogu P1091 [NOIP 2004 提高组] 合唱队形

本题只需正反分别求一次最长上升子序列,正向为 \(f[i]\) 反向为 \(g[i]\),最后 \(max(f[i]+g[i])\) 即为答案

#include<bits/stdc++.h>
using namespace std;
const int N=105;
int n,f[N],g[N],a[N],ans;
int main(){ios::sync_with_stdio(0);cin.tie(0),cout.tie(0);cin>>n;for(int i=1;i<=n;i++) cin>>a[i];for(int i=1;i<=n;i++){f[i]=1;for(int j=1;j<i;j++){if(a[i]>a[j]) f[i]=max(f[i],f[j]+1);}}for(int i=n;i;i--){g[i]=1;for(int j=n;j>i;j--){if(a[i]>a[j]) g[i]=max(g[i],g[j]+1);}}for(int i=1;i<=n;i++) ans=max(ans,f[i]+g[i]-1);cout<<n-ans;return 0;
}

luogu P2285 [HNOI2004] 打鼹鼠

本题开始都是通过巧妙转换变为最长上升子序列的题,有几类常见转换方法,在后面几道题中会一一体现

根据题意:第 \(i\) 只鼹鼠只会在 \(t_i\) 时出现,如果在此时刻没打死它,在下一时刻它就会消失,并且,在 \(t_i\) 之前鼹鼠不会出现,是打不到的。

首先,有一个易发现的性质:初始时刻站到某一只鼹鼠的坑位上,结果一定不比初始时在空白坑位差。很好理解,我直接站到坑位上,就省去了到第一只鼹鼠坑位所移动的时间。

接着,对于其中两只鼹鼠,只有时间到下一只鼹鼠出现的时候,才能打它,导致我就算提前到了下一只鼹鼠的坑位,也得等到它出现的时刻,才能打死下一只鼹鼠,这对于打死下下只鼹鼠来说,时间差就不会因为提前到下一只鼹鼠而改变。所以,对于任意两只鼹鼠,记两只鼹鼠的曼哈顿距离为 \(d\),时间之差为 \(\Delta t\),不难发现只有当 \(d≤\Delta t\) 时,才能打死下一只鼹鼠

不难发现,这里的 \(d≤\Delta t\) 和最长上升子序列的板题中的决策很像(事实上就是一样的),所以,这道题其实就转换成了,类似于求最长上升子序列的题(注意,题目保证了每只鼹鼠的出现时间是递增的,所以按出现时间排序,否则还得按出现时间排序)

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1e4+5;
int s,n,f[N],ans;
struct mouse{int t,x,y;
}m[N];
int d(int x1,int y1,int x2,int y2){return abs(x1-x2)+abs(y1-y2);
}
int main(){ios::sync_with_stdio(0);cin.tie(0),cout.tie(0);cin>>s>>n;for(int i=1;i<=n;i++) cin>>m[i].t>>m[i].x>>m[i].y;for(int i=1;i<=n;i++){f[i]=1;for(int j=1;j<i;j++){if(m[i].t-m[j].t>=d(m[i].x,m[i].y,m[j].x,m[j].y)) f[i]=max(f[i],f[j]+1);}ans=max(ans,f[i]);}cout<<ans;return 0;
}

luogu P2782 友好城市

直接说思路:

记第 \(i\) 对两岸的城市编号分别为 \(a[i]\)\(b[i]\)

先把每对城市按 \(a[i]\) 升序排序,不难发现,如果我选了 第 \(i\) 对城市,那么如果要选第 \(j\) 对城市(\(j>i\)),必须要 \(b[j]>b[i]\),否则就会有交叉,就不可以批准了。到这里都已经成了 \(LIS\) 裸题了

代码:

//本题n为2e5,所以用贪心法求LIS
#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
const int N=2e5+5;
int n,q[N],len;
pair<int,int>p[N];
int main(){cin>>n;for(int i=1;i<=n;i++) cin>>p[i].x>>p[i].y;sort(p+1,p+1+n);for(int i=1;i<=n;i++){if(len==0||p[i].y>q[len-1]) q[len++]=p[i].y;else{int x=lower_bound(q,q+len,p[i].y)-q;q[x]=p[i].y;}}cout<<len;return 0;
}

P1233 木棍加工

本题依旧按某一个参数排序,按 \(l\) 或者 \(w\) 都可以,要降序排序,然后,对另一个参数求 \(LIS\) 即可。

注意,可能会有疑问,求的最小时间,为什么答案是 \(LIS\)?这里的最小的分钟数是由降序排序保证的,而求的 \(LIS\) 是加工至少必须花的时间

#include<bits/stdc++.h>
using namespace std;
const int N=5005;
int n,q[N],len;
struct node{int l,w;
}a[N];
bool cmp(node a,node b){if(a.l==b.l) return a.w>b.w;return a.l>b.l;
}
int main(){cin>>n;for(int i=1;i<=n;i++) cin>>a[i].l>>a[i].w;sort(a+1,a+1+n,cmp);for(int i=1;i<=n;i++){if(len==0||a[i].w>q[len-1]) q[len++]=a[i].w;else{int x=lower_bound(q,q+len,a[i].w)-q;q[x]=a[i].w;}}cout<<len;return 0;
}

以上两题的转化思路是:有双参数,按其中一个参数升序或降序排序

数塔

luogu P1216 [IOI 1994 / USACO1.5] 数字三角形 Number Triangles

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1005;
int n,f[N],ans;
int main(){ios::sync_with_stdio(0);cin.tie(0),cout.tie(0);cin>>n;for(int i=1;i<=n;i++){for(int j=n-i+1;j<=n;j++){int a;cin>>a;f[j]=max(f[j],f[j+1])+a;}}for(int i=1;i<=n;i++) ans=max(ans,f[i]);cout<<ans;return 0;
}

有一点要说的,求组合数 \(C^{m}_{n} = C^{m}_{n-1} + C^{m-1}_{n-1}\),不难发现,就是数塔,并且,数塔貌似也就求组合数用的多一些,掌握组合数代码就好了。

其他

接着才是重头戏:

类似背包转移:

luogu P1077 [NOIP 2012 普及组] 摆花

思路:

设计状态:\(f_{i,j}\) 表示到第 \(i\) 朵花为止,已经用了 \(j\) 个花瓶的总方案数。

转移:

\[f_{i,j} = \Sigma_{k=0}^{min(a_i,j)} f_{i-1,j-k} \]

答案:\(f_{n,m}\)

初始化:\(f_{0,0}=1\)

#include<bits/stdc++.h>
using namespace std;
const int N=105,mod=1e6+7;
int n,m,f[N][N],a[N];
int main(){cin>>n>>m;for(int i=1;i<=n;i++) cin>>a[i];f[0][0]=1;for(int i=1;i<=n;i++){for(int j=0;j<=m;j++){for(int k=0;k<=a[i];k++){if(j>=k) f[i][j]=(f[i][j]+f[i-1][j-k])%mod;}}}cout<<f[n][m];return 0;
}

luogu P1854 [IOI 1999] 花店橱窗布置

几乎一样,只不过从方案数变成了最优解,这题要输出一种方案,用一个 \(pre\) 数组记录一下,\(f\) 由谁转移,最后递归输出即可

#include<bits/stdc++.h>
using namespace std;
const int N=105;
int n,m,a[N][N],f[N][N],pre[N][N];
void dfs(int t,int id){if(t==0) return;dfs(t-1,pre[t][id]);cout<<id<<" ";
}
int main(){cin>>n>>m;for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){cin>>a[i][j];f[i][j]=-0x3f3f3f3f;}}f[0][0]=0;for(int i=1;i<=n;i++){for(int j=i;j<=m;j++){for(int k=i-1;k<j;k++){if(f[i][j]<f[i-1][k]+a[i][j]){f[i][j]=f[i-1][k]+a[i][j];pre[i][j]=k;}}}}int ans=-0x3f3f3f3f,id;for(int i=n;i<=m;i++){if(ans<f[n][i]){ans=f[n][i];id=i;}}cout<<ans<<endl;dfs(n,id);return 0;
}

luogu P2066 机器分配

以及一样的题,这题输出的方案要按编号递增的公司分的机器单调不降的输出,此时,\(pre\) 数组只需在 \(f_{i,j}<=f_{i-1,j-k}+a_{i,k}\) 记录即可("\(=\)"就是在盈利一样时,把方案更新成符合要求的)

#include<bits/stdc++.h>
using namespace std;
int n,m,a[11][16],f[11][16],pre[11][16];
void dfs(int i,int j){if(i==0) return;dfs(i-1,j-pre[i][j]);cout<<i<<" "<<pre[i][j]<<endl;
}
int main(){cin>>n>>m;for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){cin>>a[i][j];}}for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){for(int k=0;k<=j;k++){if(f[i][j]<=f[i-1][j-k]+a[i][k]){f[i][j]=f[i-1][j-k]+a[i][k];pre[i][j]=k;}}}}cout<<f[n][m]<<endl;dfs(n,m);return 0;
}
http://www.hskmm.com/?act=detail&tid=37054

相关文章:

  • Qt/C++实现无人机监控系统/航点实时监控系统/集群地面站管理平台/飞行轨迹规划和模拟
  • 【GitHub每日速递 251023】46.1k star, 1.2B参数逆袭!MinerU2.5成最牛文档解析多模态大模型
  • 我在政和一中的求学岁月(1993-1997)
  • 互测记录
  • Python随笔(第一周)
  • 读AI赋能07基准测试
  • 微软七月补丁日修复130个漏洞,重点关注RRAS与Office安全更新
  • 比特币闪电网络开源项目
  • 图像分割- sam2 版本 - MKT
  • tryhackme-网络安全基础-AD基础- Active Directory 基础知识-20
  • tryhackme-网络安全基础-命令行- windows命令行-21
  • 图像分割和目标跟踪 - MKT
  • tryhackme-网络安全基础-开启您的网络安全之旅- 搜索技巧-19
  • ESP32 + INMP441数字麦克风 可以做哪些有趣的应用
  • Solon v3.4.7, v3.5.6, v3.6.1 发布(国产优秀应用开发框架)
  • tryhackme-预安全-windows基础-windows 基础知识3-18
  • 从生产到出库:医疗器械行业SAP B1MES质量追溯闭环方案
  • CF1430C Numbers on Whiteboard
  • SAP实施专家指南:SAP B1 如何优化成本与缩短项目周期?
  • tryhackme-预安全-windows基础-windows 基础知识2-17
  • CF1248A Integer Points
  • 10.23
  • 高级程序语言设计第二次作业
  • MIT6.824-MapReduce
  • 直流电机编码器测速
  • 搜索百科(5):Easysearch — 自主可控的国产分布式搜索引擎
  • 20251022 之所思 - 人生如梦
  • AI 赋能 + 场景破界 低代码平台的未来发展趋势
  • 迎面走来的是邪恶构造题
  • 中小企业数字化转型难?低代码的轻量化破局方案