一般DP

都是有模板的,先初始化,然后找到不同状态下数值的关系,使得某个状态可用另一个状态由一个固定的方式转移而来,列出状态转移方程,这就是DP;

例题

P1216 [USACO1.5]数字三角形 Number Triangles

  1. f[i][j]+=max(f[i-][j-],f[i-][j]);

方程

P1044 栈

  1. f[i]+=f[j]*f[i-j-];

方程

P2800 又上锁妖塔

  1. f[i]=min(f[i-]+t[i],min(f[i-]+t[i-],f[i-]+t[i-]));

方程

P1057 传球游戏

可以看出,裸的DP是几乎没有难度的,当然某些题除外如P1004P1280等题,值得思考。


背包问题

背包属于基础DP,但拓展性是最高的。

具体可以看dd大牛的《背包九讲》

以下先讲01背包

  1. f[v]=max{f[v],f[v-c[i]]+w[i]};

上面的是01背包的转移方程,但v是从V...c[i]的。

为什么呢?这个方程代表第v个体积的物体的最大值=max(他自己本身的值,v-第i个物体的体积时的最大值+第i个物体的值)

例题

01背包基本是套这个模板,但也不缺乏很有思考性的,如P2370P2979P1156P4544(这个要单调队列优化,但纯背包有60-70分,题解在这里);


线段树单调队列优化(都是用线段树水过)

首先,相信大家都知道什么是移动窗口了吧(简单得一匹好吧,线段树修改查找,傻子都会),建立一个deque(头尾都可进出,但常数可以把你卡到80,连读优都救不了我)

细节看注释吧

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. deque<int>q;
  4. int n,m,x[2000005];
  5. inline int read(){
  6. int ret=0,f=1;char ch=getchar();
  7. while (ch<'0'||ch>'9') {if (ch=='-') f=-f;ch=getchar();}
  8. while (ch>='0'&&ch<='9') ret=ret*10+ch-'0',ch=getchar();
  9. return ret*f;
  10. }
  11. int main()
  12. {
  13. cin>>n>>m;
  14. for (register int i=1;i<=n;i++) x[i]=read();
  15. cout<<0<<endl;//因为是前i个
  16. for (register int i=1;i<=n-1;i++)
  17. {
  18. while (!q.empty()&&x[i]<=x[q.back()]) q.pop_back();//维护单调递增性,因为要求最小
  19. q.push_back(i);//放入
  20. while (q.back()-q.front()>=m) q.pop_front();//过期就弹出,deque存编号就很方便了
  21. cout<<x[q.front()]<<endl;//因为单调递增,所以头最小
  22. }
  23. }

P1725森♂之妖精

如果仅仅只是N^2DP相信大家都可以A出来,方程就是f[i]=max{f[i-j...i]}+a[i],但这明显可以优化好吧(把f数组插入线段树里(logn),在查询(logn)),我们只需要f[i-j...i]的最大值就可以O(1)推了(是推),所以搞个单调队列,像滑动窗口那样做个单调递减队列,每次拿队头推即可

  1. //这是我丑陋的优先队列的代码
  2. #include<bits/stdc++.h>
  3. using namespace std;
  4. priority_queue<int>qx,qy;
  5. const int MAXN=300005;
  6. int ans[MAXN],N,L,R,f[MAXN],maxx;
  7. inline int read()
  8. {
  9. int ret=0,f=1;char ch=getchar();
  10. while (ch<'0'||ch>'9') {if (ch=='-') f=-f;ch=getchar();}
  11. while (ch>='0'&&ch<='9') ret=ret*10+ch-'0',ch=getchar();
  12. return ret*f;
  13. }
  14. inline bool check(){
  15. if (!qy.empty()&&qx.top()==qy.top()) return 1;
  16. return 0;
  17. }
  18. int main(){
  19. N=read();L=read();R=read();
  20. for (int i=0;i<=N;i++) ans[i]=read();
  21. for(int i=1;i<=L-1;i++) qy.push(ans[i]);
  22. for(int i=L;i<=N;i++){
  23. qx.push(f[i-L]);
  24. if(i-R-1>=L) qy.push(f[i-R-1]);
  25. if (check()==1) qx.pop(),qy.pop();
  26. f[i]=qx.top()+ans[i];
  27. }
  28. maxx=-99999999;
  29. for (int i=N-R+1;i<=N;i++) maxx=max(maxx,f[i]);
  30. cout<<maxx<<endl;
  31. return 0;
  32. }
  1. //这是luogu上我认为(用deque)比较好看的单调队列代码
  2. #include <bits/stdc++.h>
  3. using namespace std;
  4. void read(int &x){
  5. int f=1,r=0;char ch;
  6. do ch=getchar();while(ch!='-'&&!isdigit(ch));
  7. if(ch=='-')f=-1,ch=getchar();
  8. do r=r*10+ch-48,ch=getchar();while(isdigit(ch));
  9. x=f*r;
  10. }
  11. int n,l,r;
  12. struct info{
  13. int num,val;
  14. //分别记录序号和数值,序号用来判断是否超出范畴
  15. };
  16. int f[200010];//f[i]表示到达点i时可获得的最大冰冻指数
  17. deque<info> q;//STL tql,单调队列非常方便
  18. int a[200010];//a为点i的冰冻指数(输入数据)
  19. int main() {
  20. cin>>n>>l>>r;
  21. for(int i=0;i<=n;i++)read(a[i]);
  22. for(int i=l;i<=n;i++){
  23. while(!q.empty()&&q.back().val<f[i-l])q.pop_back();//弹出较小值
  24. q.push_back((info){i-l,f[i-l]});//放当前值
  25. if(i-q.front().num>r-l)q.pop_front();//弹出超过范畴值
  26. f[i]=q.front().val+a[i];//记录最大值加上该点的冰冻指数放入f[i]
  27. }
  28. cout<<*max_element(f+n-r+1,f+n+1)<<endl;
  29. //注意输出的是这一段之间的最大值,因为这一段之间的每一位都可以下一步跳出n
  30. return 0;
  31. }

最后,祭出NOIP2017 T4你以为我会再写吗?其实我写过了啊!

例题

P2422(注意,并非一般单调队列啊,并且几乎不能叫DP,但需要思考时间)


优先队列优化(题少不怎么打)

不写了,单调做的几乎都可以用这个做

要看就看我停课集训DAY1 T3


状压DP(就是变相暴力)

说道状压DP,就不得不说著名的TSP旅行商问题了,这道题需要用到状压DP。

首先先上状压DP基操

这就是基操,状压DP一般都是把状态用二进制数表示出来,一般都是取或不取之类的01状态;

接下来讲解旅行商问题。

首先确定数组维数:2维,因为一维太难推了,且有后效性。二维表示什么呢?分别表示当前状态和最后走到何处。

然后就是枚举状态和循环了

第一层循环 i 枚举每个状态

第二层循环 j 枚举下一步到达的点

第三层枚举从k点走到j点

然后if ((1<<(j-1)&i)==0)表示j点在此状态还未走到,if ((1<<k-1)&i)表示k点在此状态已走到。(废话)

DP[j][i|(1<<j-1)]=min(DP[j][i|(1<<j-1)],DP[k][i]+LIS[k][j])就是对比走来和当前花费了(真的暴力-1s)

然后就是喜闻乐见的代码放送了

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. int DP[25][(1<<20)-1],N,MINN=2e9,LIS[25][25];
  4. int main()
  5. {
  6. cin>>N;
  7. int MAXN=(1<<N)-1;
  8. for (int i=1;i<=N;i++)
  9. for (int j=1;j<=N;j++)
  10. cin>>LIS[i][j];
  11. memset(DP,0x3f,sizeof(DP));
  12. DP[1][1]=0;//初始化
  13. for (int i=0;i<=MAXN;i++)
  14. {
  15. for (int j=1;j<=N;j++)//到达j点
  16. if ((1<<(j-1)&i)==0)//到达点未被走过
  17. for (int k=1;k<=N;k++)//从k过来
  18. {
  19. if ((1<<k-1)&i) DP[j][i|(1<<j-1)]=min(DP[j][i|(1<<j-1)],DP[k][i]+LIS[k][j]);
  20. }
  21. }
  22. for (int i=2;i<=N;i++) MINN=min(MINN,DP[i][(1<<N)-1]+LIS[i][1]);//要走回去啊,但不能不走(所以i从2开始)
  23. cout<<MINN<<endl;
  24. return 0;
  25. }

luogu的P3092是道很好的题目

但在此只放代码,有注释的

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. int m,n,a[100010],sum[100010],c[20],b[20],f[1<<16],k,maxx=-1,sum1;
  4. int main(){
  5. cin>>k>>n;
  6. for(int i=1;i<=k;i++) cin>>c[i],sum1+=c[i];
  7. for(int i=1;i<=n;i++) cin>>a[i],sum[i]=sum[i-1]+a[i];
  8. for(int i=0;i<=(1<<k)-1;i++)//硬币状态
  9. for(int j=1;j<=k;j++)
  10. if((i&1<<j-1)){//没有被选
  11. int wz=f[i^1<<j-1];
  12. wz=upper_bound(sum+1,sum+n+1,sum[wz]+c[j])-sum;//买连续一段店铺
  13. f[i]=max(f[i],wz-1);//更新状态
  14. }
  15. for(int i=0;i<=(1<<k)-1;i++)
  16. if(f[i]==n){//个数满足
  17. int sum=0;
  18. for(int j=1;j<=k;j++) if(i&1<<j-1) sum+=c[j];//如果是1代表用了
  19. maxx=max(maxx,sum1-sum);//剩下最大价值
  20. }
  21. if(maxx<0) cout<<-1<<endl;else cout<<maxx<<endl;
  22. return 0;
  23. }

果然,状压DP是一种很优美暴力的做法呢

例题

P1879P1896


树形DP(我最不擅长)

蒟蒻真的不会啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊!!!


其他算法套DP(二分什么的)

NOIP2017 T4 二分加单调队列+DP题解

DAY3 T4 luogu P1772 最短路+DP(真的少见)


还有一些提高组的就不列了(如斜率优化,数位,插头DP)

算法之DP的更多相关文章

  1. HDU4612(Warm up)2013多校2-图的边双连通问题(Tarjan算法+树形DP)

    /** 题目大意: 给你一个无向连通图,问加上一条边后得到的图的最少的割边数; 算法思想: 图的边双连通Tarjan算法+树形DP; 即通过Tarjan算法对边双连通缩图,构成一棵树,然后用树形DP求 ...

  2. 算法-动态规划DP小记

    算法-动态规划DP小记 动态规划算法是一种比较灵活的算法,针对具体的问题要具体分析,其宗旨就是要找出要解决问题的状态,然后逆向转化为求解子问题,最终回到已知的初始态,然后再顺序累计各个子问题的解从而得 ...

  3. 算法-数位dp

    算法-数位dp 前置知识: \(\texttt{dp}\) \(\texttt{Dfs}\) 参考文献 https://www.cnblogs.com/y2823774827y/p/10301145. ...

  4. LOJ #2540. 「PKUWC 2018」随机算法(概率dp)

    题意 LOJ #2540. 「PKUWC 2018」随机算法 题解 朴素的就是 \(O(n3^n)\) dp 写了一下有 \(50pts\) ... 大概就是每个点有三个状态 , 考虑了但不在独立集中 ...

  5. $2019$ 暑期刷题记录1:(算法竞赛DP练习)

    $ 2019 $ 暑期刷题记录: $ POJ~1952~~BUY~LOW, BUY~LOWER: $ (复杂度优化) 题目大意:统计可重序列中最长上升子序列的方案数. 题目很直接的说明了所求为 $ L ...

  6. 从最长公共子序列问题理解动态规划算法(DP)

    一.动态规划(Dynamic Programming) 动态规划方法通常用于求解最优化问题.我们希望找到一个解使其取得最优值,而不是所有最优解,可能有多个解都达到最优值. 二.什么问题适合DP解法 如 ...

  7. 动态规划 算法(DP)

    多阶段决策过程(multistep decision process)是指这样一类特殊的活动过程,过程可以按时间顺序分解成若干个相互联系的阶段,在每一个阶段都需要做出决策,全部过程的决策是一个决策序列 ...

  8. 五大常用算法之二:动态规划算法(DP)

    一.基本概念 动态规划过程是:每次决策依赖于当前状态,又随即引起状态的转移.一个决策序列就是在变化的状态中产生出来的,所以,这种多阶段最优化决策解决问题的过程就称为动态规划. 二.基本思想与策略 基本 ...

  9. 【算法】DP解决旅行路径问题

    问题描述 : After coding so many days,Mr Acmer wants to have a good rest.So travelling is the best choice ...

随机推荐

  1. 【大数据】SparkSql学习笔记

    第1章 Spark SQL概述 1.1 什么是Spark SQL Spark SQL是Spark用来处理结构化数据的一个模块,它提供了2个编程抽象:DataFrame和 DataSet,并且作为分布式 ...

  2. Java8新特性之Stream

    原文链接:http://ifeve.com/stream/ Java8初体验(二)Stream语法详解 感谢同事[天锦]的投稿.投稿请联系 tengfei@ifeve.com上篇文章Java8初体验( ...

  3. BZOJ2001 [Hnoi2010]City 城市建设 CDQ分治

    2001: [Hnoi2010]City 城市建设 Time Limit: 20 Sec  Memory Limit: 162 MB Description PS国是一个拥有诸多城市的大国,国王Lou ...

  4. MySQL -- 主从复制的可靠性与可用性

    主库A执行完成一个事务, 写入binlog ,记为 T1 然后传给从库B,从库B 接收该binlog ,记为 T2 从库B执行完成这个事务,记为 T3 同步延时: T3-T1 同一个事务,在 从库执行 ...

  5. 【BZOJ1296】[SCOI2009]粉刷匠(动态规划)

    [BZOJ1296][SCOI2009]粉刷匠(动态规划) 题面 BZOJ 洛谷 题解 一眼题吧. 对于每个串做一次\(dp\),求出这个串刷若干次次能够达到的最大值,然后背包合并所有的结果即可. # ...

  6. 【转】结构struct 联合Union和枚举Enum的细节讨论

    结构struct 联合Union和枚举Enum的细节讨论 联合(Union)是一种构造数据类型,它提供了一种使不同类型数据类型成员之间共享存储空间的方法,同时可以实现不同类型数据成员之间的自动类型转换 ...

  7. 获取C++类成员虚函数地址

    1.GCC平台 GCC平台获取C++成员虚函数地址可使用如下方法[1]: class Base{ int i; public: virtual void f1(){ cout<<" ...

  8. ByteBuffer的allocate与allocateDirect2013-01-11

    在Java中当我们要对数据进行更底层的操作时,通常是操作数据的字节(byte)形式,这时常常会用到ByteBuffer这样一个类.ByteBuffer提供了两种静态实例方式:   public sta ...

  9. centos7下设置opencv环境变量

    最近要装YOLO,但是MAKE的时候总是找不到OPENCV的路径, 原因是:我以前卸载过一次OPENCV,然后自己重新安装了opencv2.4.10,  因为当时只在QT 中用,所以编译完也没有设置环 ...

  10. 3分钟读懂移动端rem使用方法

    1.为什么要用rem 博客很久没写了,原因很简单. 最近接手了一个项目,要同时做PC和移动端的页面,之前没接触过,但毕竟给钱的是大爷,所以还是硬着头皮上了. 移动端最麻烦的是什么? 不同分辨率适配! ...