Part 00 dp 什么时候用?

首先,如果这题数据范围一眼 BFS/DFS/暴力/随机化 可以过,那么还写 dp 干什么

但是,但你决定这题要贪心时,我建议咱还是要看一下它对不对

整一个石子合并这样的就很难受

其次,除了数位 dp 以外正常 dp 数据范围不会特别大 至少要保证 DP 数组不会 MLE

最重要的是,dp 的根本思路是记录以前的信息进行推算

动态规划是一种通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。 —— OI-WiKi

听着有点分治的意思

同时 dp 一般求的是最优解(数位 dp 除外)

很多人都强调,dp 的问题不能有后效性,这个我们通过以后例题来看 作者表示自己不懂

最后,dp 里有两个东西叫状态和转移

状态是指当前的一个状态

例如 "现在作者很蒟蒻" 这就是一个状态

转移则是指当前状态和上一步状态之间的关系

for example

现在作者=(一年前更蒟蒻的作者+一年的学习)

这就是一个转移

转移也可能是 现在作者=(一年前一样蒟蒻的作者+一年的唐)

Part 01 简单 dp

有一部分的 dp 题目没有任何的技巧

在 OI 中属于送分题

接下来我们直接

上!例!题!

T1 ACP1915 数字金字塔

题面友好不多解释

直接 \(Solution\)

设 \(dp[i][j]\) 为在 \([i,j]\) 位置的最大收益

难点在于处理金字塔

如图所示

图中我们发现 \(dp[i][j]\) 实际上是从 \(dp[i-1][j]\) 和 \(dp[i-1][j-1]\) 转移而来的(图中的粉色点是由蓝色点转移而来的)

\(So\) 转移方程也很简单啦

dp[i][j]=max(dp[i-1][j-1],dp[i-1][j])+mp[i][j])

现在思考边界

发现不用处理设 \(0\) 即可

由于数据非负也不需要特判了

\(Code\)

#include<bits/stdc++.h>
#define Routine for(int i=1;i<=n;i++)for(int j=1;j<=i;j++)
using namespace std;
const int N=510;
int mp[N][N],dp[N][N],n,ans=-1;
int main(){
scanf("%d",&n);
Routine scanf("%d",&mp[i][j]);
Routine dp[i][j]=max(dp[i-1][j-1],dp[i-1][j])+mp[i][j];
for(int i=1;i<=n;i++)ans=max(ans,dp[n][i]);
printf("%d\n",ans);
return 0;
}

$\ $

T2 B3637 最长上升子序列

设 \(dp[i]\) 为以第 \(i\) 个数结尾的最长上升子序列长度

只要在前面找一个结尾 \(value\) 比他小的最长的最长上升子序列接上去就对了

\(Code\)

#include<bits/stdc++.h>
using namespace std;
const int N=5505;
int lable[N],dp[N],n,tmp;
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&lable[i]);
for(int i=1;i<=n;i++){
tmp=0;
for(int j=1;j<i;j++)if(lable[i]>lable[j])tmp=max(tmp,dp[j]);
dp[i]=tmp+1;
}
tmp=0;
for(int i=1;i<=n;i++)tmp=max(tmp,dp[i]);
printf("%d\n",tmp);
return 0;
}

$\ $

T2.1 ACP1916 最长不降子序列

题目基本同 T2

AcCoders 硬核题目又双叒叕写错了,就是最长上升

只不过要求输出方案

只要记录转移的来源 DFS 即可(很多题都是如此)

为此还需要开一个虚点来记录末尾的位置

Code

#include<bits/stdc++.h>
using namespace std;
const int N=5505;
int lable[N],dp[N],n,tmp,GoPrevious[N];
void Dfs(int id){
if(!~id)return;
Dfs(GoPrevious[id]);
if(id==n)return;
printf("%d ",lable[id]);
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&lable[i]);
++n;
lable[n]=107473813;
for(int i=1;i<=n;i++){
tmp=0,GoPrevious[i]=-1;
for(int j=1;j<i;j++)if(lable[i]>lable[j])if(tmp<=dp[j]/*AcCoders 无 SPJ 要求 tmp 处取等*/)tmp=dp[j],GoPrevious[i]=j;
dp[i]=tmp+1;
//printf("dp[%d]=%d prev[%d]=%d\n",i,dp[i],i,GoPrevious[i]);
}
printf("max=%d\n",dp[n]-1);
Dfs(n);
return 0;
}
/*
7
1 3 2 4 3 5 3
*/
T2.2 NOIP1999 导弹拦截

题面非常清晰

第一问所求就是最长不降子序列

并且还有善良的 SPJ

\(So\ 40/200\ pts\ Code...\)

#include<bits/stdc++.h>
using namespace std;
const int N=1009800;
int lable[N],dp[N],n,tmp;
int main(){
while(scanf("%d",&lable[++n])!=EOF);
lable[n]=-1;
for(int i=1;i<=n;i++){
tmp=0;
for(int j=1;j<i;j++)if(lable[i]<=lable[j])tmp=max(tmp,dp[j]);
dp[i]=tmp+1;
}
printf("%d\n0",dp[n]-1);
return 0;
}

发现此时 \(O(n^2)\) 复杂度第一问已经拿到了 \(AC\)

考虑如何处理第二问

这是一个贪心的思想

只需将每一个系统最后一发炮弹的高度记录下来

判断能否加入以前的系统

若可以则加入合法的高度最低的系统

若不行就新开一个系统

可证这样做后序列满足单调不降

于是找最低合法系统就可以二分

总复杂度 \(O(n\ log\ n)\)

可以过掉所有数据

经过适当的特判避免 \(TLE\) 卡掉第二问分数

能拿 \(140\ pts\)

AcCoders ACP1917 已经可以了

Code

#include<bits/stdc++.h>
#define mid ((l+r)>>1)
using namespace std;
const int N=1009800;
int lable[N],dp[N],n,ender[N],cnt;
void Solve_First(){
if(n>20000){
printf("0");
return;
}
int tmp;
for(int i=1;i<=n;i++){
tmp=0;
for(int j=1;j<i;j++)if(lable[i]<=lable[j])tmp=max(tmp,dp[j]);
dp[i]=tmp+1;
}
printf("%d",dp[n]-1);
}
int Shit(int key){
int l=1,r=cnt;
while(l<r){
if(ender[mid]>=key)r=mid;
else l=mid+1;
}
return l;
}
void Solve_Second(){
for(int i=1;i<n;i++){
if(cnt==0||ender[cnt]<lable[i])ender[++cnt]=lable[i];
else ender[Shit(lable[i])]=lable[i];
}
printf("%d",cnt);
}
int main(){
while(scanf("%d",&lable[++n])!=EOF);
lable[n]=-1;
Solve_First();
puts("");
Solve_Second();
return 0;
}

现在思考如何 \(O(n\ log\ n)\) 处理最长不升子序列

这个方法也有很多,此处我们使用树状数组方法

我们重新分析一下求最长不升子序列的过程

首先枚举以每一个元素为结尾 很明显无法压缩时间复杂度

接下来找到前面 \(value\) 比他小的 \(dp\) 值最大的位置并接上去

这个东西有点类似于二维偏序

可以用树状数组维护

瞅一眼炮弹高度数据范围 \([1,50000]\)

根本不需要加一或者离散化之类的操作

直接填进去维护最小值即可

蒟蒻作者能不能写出来也很存疑

注意此处要求 \(value\) 大于当前元素的

所以我们可以用一个数减去 \(value\) 作为下标存进树状数组

\(Code\)

#include<bits/stdc++.h>
#define mid ((l+r)>>1)
#define lowbit (id&-id)
using namespace std;
const int N=100980,M=150073;
class Tree{
public:
void set_size(int _size){size=_size;}
void modify(int id,int value){while(id<=size)data[id]=max(data[id],value),id+=lowbit;}
int query(int id){
int ans=-1;
while(id>=1)ans=max(ans,data[id]),id-=lowbit;
return ans;
}
private:
int data[M+10],size;
};
Tree tree;
int lable[N],dp[N],n,ender[N],cnt;
void Solve_First(){
int ans=-1;
for(int i=1;i<=n;i++){
dp[i]=tree.query(50089-lable[i])+1;
ans=max(ans,dp[i]);
tree.modify(50089-lable[i],dp[i]);
}
printf("%d",ans);
}
int Shit(int key){
int l=1,r=cnt;
while(l<r){
if(ender[mid]>=key)r=mid;
else l=mid+1;
}
return l;
}
void Solve_Second(){
for(int i=1;i<=n;i++){
if(cnt==0||ender[cnt]<lable[i])ender[++cnt]=lable[i];
else ender[Shit(lable[i])]=lable[i];
}
printf("%d",cnt);
}
int main(){
tree.set_size(M);
while(scanf("%d",&lable[++n])!=EOF);
--n;
Solve_First();
puts("");
Solve_Second();
return 0;
}

成功 \(AC\) luogu 题目

AcCoders 双倍经验

T3 ACP1918 城市交通路网

本题题面抽象

简单说就是给一个邻接矩阵

跑出从一号点到 \(n\) 号点的最短路

数据范围没给 AcCoders 不愧是他

推测应该 \(1<=n<=2000\)

也MarkDown的不知道有没有负权

要求输出距离和路径

作者也不知道这和 \(DP\) 有啥关系

写个 Dijkstra 水过吧

思考如何输出路径

考虑记录每个点的前驱

在元素到达堆顶时记录

Code

#include<bits/stdc++.h>
using namespace std;
const int N=100073;
struct line{int to,len;};
struct Node{
int id,key,Previous;
bool operator <(const Node &rhs)const{
return key>rhs.key;
}
};
priority_queue<Node> q;
vector<line> lable[N];
int Distance[N],Previous[N],n;
bool vis[N];
void Dijkstra(int start){
memset(Distance,0x3f3f3f,sizeof(Distance));
Distance[start]=0;
q.push({start,0,-1});
while(!q.empty()){
Node top=q.top();
q.pop();
if(vis[top.id])continue;
vis[top.id]=true;
Previous[top.id]=top.Previous;
for(auto it:lable[top.id]){
if(Distance[it.to]>=Distance[top.id]+it.len){
Distance[it.to]=Distance[top.id]+it.len;
q.push({it.to,Distance[it.to],top.id});
}
}
}
}
void Dfs(int id){
if(~id){
Dfs(Previous[id]);
printf("%d ",id);
}
}
int main(){
int tmp;
scanf("%d",&n);
for(int i=1;i<=n;i++)for(int j=1;j<=n;j++){
scanf("%d",&tmp);
if(tmp)lable[i].push_back({j,tmp});
}
Dijkstra(1);
printf("minlong=%d\n",Distance[n]);
Dfs(n);
puts("");
return 0;
}
T4 ACP1919 挖地雷

这题题面还是比较清晰的

如果不细看这题是一道

\(Dijkstra+分层图+卡常+数据结构\) 的题

直到你看到了

保证 \(x<y\)

于是这个就没有后效性了

可以用 \(DP\) 来做

设 dp[i] 为到了第 i 个地窖的最大收益

只需要在自己的前驱中找到受益最大的加上当前地窖收益即可

注意最终答案不一定是 \(dp[n]\)

需要取 \(DP_{max}\)

至于输出路径 \(DFS\) 即可

Code

#include<bits/stdc++.h>
using namespace std;
const int N=1009;
int n,lable[N],dp[N],Front[N];
vector<int> Previous[N];
void Dfs(int id){
if(id==-1){
puts("Holy Shit!");
return;
}
if(Front[id]==-1){
printf("%d",id);
return;
}else{
Dfs(Front[id]);
printf("-%d",id);
}
}
int main(){
int tmp1,tmp2,tmp;
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&lable[i]);
while(true){
scanf("%d%d",&tmp1,&tmp2);
if(tmp1==0||tmp2==0||tmp1>tmp2)break;
Previous[tmp2].push_back(tmp1);
}
for(int i=1;i<=n;i++){
tmp=0,Front[i]=-1;
for(auto it:Previous[i])if(dp[it]>tmp)tmp=dp[it],Front[i]=it;
dp[i]=tmp+lable[i];
}
tmp=-1,tmp1=-1;
for(int i=1;i<=n;i++)if(tmp<dp[i])tmp=dp[i],tmp1=i;
Dfs(tmp1);
printf("\n%d",tmp);
return 0;
}

\(upd(2025/3/27)\):作者已经在上次更新之后很久未更了

结果发现已经写不出 DP 橙题

于是来更一下

T5 P1541 乌龟棋

这题的数据范围搜索只有 \(30\)

然后我们可以思考设四维表示各种卡片的数量

设 \(DP[a][b][c][d]\) 表示四种卡片各有 \(a,b,c,d\) 张

则可以枚举用掉的数量

然后搞一搞分数和转移即可

你要问怎么搞让他们的和符合条件?

很简单啊,暴力枚举判断!

#include<bits/stdc++.h>
using namespace std;
int n,m,pt[352],dp[41][41][41][41],card[5];
int Max(int a,int b,int c,int d){return max(max(a,b),max(c,d));}
int main(){
int tmp;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&pt[i]);
for(int i=1;i<=m;i++)scanf("%d",&tmp),++card[tmp];
dp[card[1]][card[2]][card[3]][card[4]]=pt[1];
for(int i=1;i<=m;i++){
for(int a=0;a<=card[1];a++)for(int b=0;b<=card[2];b++)for(int c=0;c<=card[3];c++)for(int d=0;d<=card[4];d++){
if(a+b+c+d+i!=m)continue;
dp[a][b][c][d]=Max(dp[a+1][b][c][d],dp[a][b+1][c][d],dp[a][b][c+1][d],dp[a][b][c][d+1])+pt[1+(card[1]-a)+(card[2]-b)*2+(card[3]-c)*3+(card[4]-d)*4];
}
}
printf("%d",dp[0][0][0][0]);
return 0;
}

或者你也可以稍微优化一点

#include<bits/stdc++.h>
using namespace std;
int n,m,pt[352],dp[41][41][41][41],card[5];
int Max(int a,int b,int c,int d){return max(max(a,b),max(c,d));}
int main(){
int tmp;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&pt[i]);
for(int i=1;i<=m;i++)scanf("%d",&tmp),++card[tmp];
dp[card[1]][card[2]][card[3]][card[4]]=pt[1];
for(int i=1;i<=m;i++){
for(int a=card[1];a>=card[1]-i&&a>=0;a--)for(int b=card[2];b>=card[2]-i&&b>=0;b--)for(int c=card[3];c>=card[3]-i&&c>=0;c--)for(int d=card[4];d>=card[4]-i&&d>=0;d--){
if(a+b+c+d+i!=m)continue;
dp[a][b][c][d]=Max(dp[a+1][b][c][d],dp[a][b+1][c][d],dp[a][b][c+1][d],dp[a][b][c][d+1])+pt[1+(card[1]-a)+(card[2]-b)*2+(card[3]-c)*3+(card[4]-d)*4];
}
}
printf("%d",dp[0][0][0][0]);
return 0;
}

Part 02 背包 DP

背包 DP 包含 01 背包,完全背包,多重背包和分组背包等等

我们看题再来搞

T1 P5020 货币系统

这道题看起来很难

让你求最小的 \(m\)

我们直接搞不太好办

但是这个题面其实就是问你:有哪些面值可以被替换

所以这个题就简单一些了

根据常识,小面额的钞票可能可以替代大面额的

所以我们把面额 \(sort\) 一下

然后就可以进行 \(DP\) 了

我们可以只用一个 \(bool\) 数组标记此面额能否被替代

然后枚举种类和面额

\(DP\) 转移式 \(DP[i][j]|=DP[i-1][j-val[i]*k]\)

然后你发现这个 \(i\) 用处不大

每次都只用到了上一层

我们通过改变一些枚举顺序可以实现一维 \(DP\)

但如果你仔细看一下你就会发现这个题他的复杂度是 \(O(nm^2)\) 的

如果数据较强根本卡不过

我们发现我们枚举值域的时候其实可以从本层转移而来

也就是 \(DP[j]|=DP[j-val[i]]\)

这其实就是一个完全背包的 \(O(nm)\) 优化

\(Code\)

#include<bits/stdc++.h>
using namespace std;
int k[111];
bool flg[25555];
int main(){
int T,n,cnt=0;
scanf("%d",&T);
while(T--){
scanf("%d",&n),memset(flg,0,sizeof flg),flg[0]=1,cnt=0;
for(int i=1;i<=n;i++)scanf("%d",&k[i]);
sort(k+1,k+n+1);
for(int i=1;i<=n;i++){
if(flg[k[i]]){++cnt;continue;}
for(int j=k[i];j<=25000;j++)flg[j]|=flg[j-k[i]];
}
printf("%d\n",n-cnt);
}
return 0;
}
T2 P5322 排兵布阵

DP——从入门到放弃 [Did Not Finish]的更多相关文章

  1. Struts2入门到放弃

    写在前面------------------------------------------------------------------------- 本文章主要从三个方面来学习Struts2框架 ...

  2. Linux从入门到放弃、零基础入门Linux(第四篇):在虚拟机vmware中安装centos7.7

    如果是新手,建议安装带图形化界面的centos,这里以安装centos7.7的64位为例 一.下载系统镜像 镜像文件下载链接https://wiki.centos.org/Download 阿里云官网 ...

  3. CYQ.Data 从入门到放弃ORM系列:开篇:自动化框架编程思维

    前言: 随着CYQ.Data 开始回归免费使用之后,发现用户的情绪越来越激动,为了保持这持续的激动性,让我有了开源的念头. 同时,由于框架经过这5-6年来的不断演进,以前发的早期教程已经太落后了,包括 ...

  4. [精品书单] C#/.NET 学习之路——从入门到放弃

    C#/.NET 学习之路--从入门到放弃 此系列只包含 C#/CLR 学习,不包含应用框架(ASP.NET , WPF , WCF 等)及架构设计学习书籍和资料. C# 入门 <C# 本质论&g ...

  5. OpenStack从入门到放弃

    OpenStack从入门到放弃 目录: 为何选择云计算/云计算之前遇到的问题 什么是云计算 云服务模式 云应用形式 传统应用与云感知应用 openstack及其相关组件介绍 flat/vlan/gre ...

  6. 绕过校园网的共享限制 win10搭建VPN服务器实现--从入门到放弃

    一.开篇立论= =.. 上次说到博主在电脑上搭建了代理服务器来绕过天翼客户端的共享限制,然而经过实际测试还不够完美,所以本着生命不息,折腾不止的精神,我又开始研究搭建vpn服务器= =... (上次的 ...

  7. 《区块链:从入门到放弃》之obc安装步骤

    obc安装步骤 朋友们可能会好奇,厨师不研究菜谱怎么改研究兵法了,哈哈,我原本是app出身,最近被安排去预研区块链和比特币技术,2个月下来,颇有斩获.期间得到IBM的CC同学指导我一步一步安装obc的 ...

  8. win10搭建代理服务器实现绕过校园网的共享限制--从入门到放弃

    博主所在学校特别坑爹,校园网被电信一家垄断了,而且最恶心的还是电信要求一条网线只能供一台电脑上网,不许接路由器共享网络= =- (还有电信2M价格是380+每年,20m是500每年,而且网速都很慢= ...

  9. WPF从入门到放弃系列第二章 XAML

    本文是作者学习WPF从入门到放弃过程中的一些总结,主要内容都是对学习过程中拜读的文章的整理归纳. 参考资料 XAML 概述 (WPF):https://msdn.microsoft.com/zh-cn ...

  10. Android -- 带你从源码角度领悟Dagger2入门到放弃

    1,以前的博客也写了两篇关于Dagger2,但是感觉自己使用的时候还是云里雾里的,更不谈各位来看博客的同学了,所以今天打算和大家再一次的入坑试试,最后一次了,保证最后一次了. 2,接入项目 在项目的G ...

随机推荐

  1. 记一次cannot access its superinterface问题的的排查 → 强如Spring也一样写Bug

    开心一刻 昨天在幼儿园,领着儿子在办公室跟他班主任聊他的情况 班主任:皓瑟,你跟我聊天是不是紧张呀 儿子:是的,老师 班主任:不用紧张,我虽然是你的班主任,但我也才22岁,你就把我当成班上的女同学 班 ...

  2. 单点登录-OAuth2

    单点登录的实现原理 单点登录在现在的系统架构中广泛存在,他将多个子系统的认证体系打通,实现了一个入口多处使用,而在架构单点登录时,也会遇到一些小问题,在不同的应用环境中可以采用不同的单点登录实现方案来 ...

  3. 项目PMP之七项目成本管理

    项目PMP之七--项目成本管理   一.定义:在预算内管理成本:预测项目成本 核心理念:重点关注项目活动的成本:同时决策的影响.相关方的不同时间不同方法的测算 趋势:挣值进度(ES)逻辑:敏捷的方式则 ...

  4. 0515-BufferedInputStream缓存输入输出流

    package A10_IOStream; import java.io.*; /* 作为IO流的入门,今天我们见识一些更强大的流.比如能够高效读写的缓冲流,能够转换编码的转换流,能够持久化存储对象的 ...

  5. react给当前元素添加一个类以及key的作用

    给当前元素添加一个类是通过className来处理的: 引入css;直接from XXXX import React, { Component } from "react"; // ...

  6. 链路状态路由协议OSPF-02

    路由信息传递与路由计算分离. 基于SPF算法. 以"累计链路开销"作为选路参考值. 所谓Link State(链路状态)指的就是路由器的接口状态.在OSPF中路由器的某一接口的链路 ...

  7. SecureCRT 屏幕输出行数设置

    第一步:打开设置 第二步:设置历史展示行数 第三步:保存后重启软件即可

  8. 1个小技巧彻底解决DeepSeek服务繁忙!

    DeepSeek 是国内顶尖 AI 团队「深度求索」开发的多模态大模型,具备数学推理.代码生成等深度能力,堪称"AI界的六边形战士". DeepSeek 最具代表性的标签有以下两个 ...

  9. Luogu P10842 Piggy and Trees 题解 [ 绿 ] [ 拆边 ] [ 贡献思维 ] [ 组合数学 ]

    Piggy and Trees:把路径拆成边的思维题. 思路 一看到这题的路径,就想到了 Luogu P3177 树上染色 这题化路径为边的贡献,分别计算的思维. 那么对于此题,先来观察题目里式子的意 ...

  10. 支付宝 v3 自签名如何实现

    今天在看文档的时候,发现支付宝新出了一个 v3 版本的接口调用方式,感觉有点意思,花了点时间研究了下这个版本要怎么实现自签名,大家有兴趣可以看看. 什么是支付宝 API v3 版本? 官网上给的解释是 ...