一般DP

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

例题

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

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

方程

P1044 栈

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

方程

P2800 又上锁妖塔

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

方程

P1057 传球游戏

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


背包问题

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

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

以下先讲01背包

 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,连读优都救不了我)

细节看注释吧

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

P1725森♂之妖精

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

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

最后,祭出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)

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

#include<bits/stdc++.h>
using namespace std;
int DP[25][(1<<20)-1],N,MINN=2e9,LIS[25][25];
int main()
{
	cin>>N;
	int MAXN=(1<<N)-1;
	for (int i=1;i<=N;i++)
	 for (int j=1;j<=N;j++)
	  cin>>LIS[i][j];
	memset(DP,0x3f,sizeof(DP));
	DP[1][1]=0;//初始化
	for (int i=0;i<=MAXN;i++)
	{
		for (int j=1;j<=N;j++)//到达j点
		if ((1<<(j-1)&i)==0)//到达点未被走过
		 for (int k=1;k<=N;k++)//从k过来
		 {
		  if ((1<<k-1)&i) DP[j][i|(1<<j-1)]=min(DP[j][i|(1<<j-1)],DP[k][i]+LIS[k][j]);
		 }
	}
	for (int i=2;i<=N;i++) MINN=min(MINN,DP[i][(1<<N)-1]+LIS[i][1]);//要走回去啊,但不能不走(所以i从2开始)
	cout<<MINN<<endl;
	return 0;
}

luogu的P3092是道很好的题目

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

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

果然,状压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. spring中的传播性 个人认为就是对方法的设置 其作用能传播到里面包含的方法上

    spring中的传播性 个人认为就是对方法的设置 其作用能传播到里面包含的方法上

  2. 小菜菜mysql练习解读分析1——查询" 01 "课程比" 02 "课程成绩高的学生的信息及课程分数

    查询" 01 "课程比" 02 "课程成绩高的学生的信息及课程分数 好的,第一道题,刚开始做,就栽了个跟头,爽歪歪,至于怎么栽跟头的 ——需要分析题目,查询的是 ...

  3. Linux Deploy Ubuntu安装MySQL

    一.在Android手机安装Linux 二.Ubuntu安装Mysql 建议在root用户上操作 sudo su 输入密码 (一)安装mysql 1. sudo apt-get install mys ...

  4. MT【203】连续型的最值

    (北大自招)已知$-6\le x_i\le 10 (i=1,2,\cdots,10),\sum\limits_{i=1}^{10}x_i=50,$当$\sum\limits_{i=1}^{10}x^2 ...

  5. 洛谷P3960 列队(NOIP2017)(Splay)

    洛谷题目传送门 最弱的Splay...... 暴力模拟30分(NOIP2017实际得分,因为那时连Splay都不会)...... 发现只是一个点从序列里搬到了另一个位置,其它点的相对位置都没变,可以想 ...

  6. NOIP2018备考——DP专题练习

    P4095 [HEOI2013]Eden 的新背包问题   P2657 [SCOI2009]windy数   P3413 SAC#1 - 萌数   P3205 [HNOI2010]合唱队   P476 ...

  7. 文件操作,内置函数open()

    先看看官方说明: The default mode is 'r' (open for reading text, synonym of 'rt'). For binary read-write acc ...

  8. Oracle和SQL SERVER在SQL语句上的差别

    Oracle与Sql server都遵循SQL-92标准:http://owen.sj.ca.us/rkowen/howto/sql92F.html,但是也有一些不同之处,差别如下: Oracle中表 ...

  9. Angular的依赖注入(依赖反转)原理说明

    依赖注入(依赖反转)意思是由函数决定要引入什么样的依赖: let mod = angular.module('test',[]); mod.controller('test_c',function($ ...

  10. 在ajax请求后台时在请求标头RequestHeader加token

    情景:为了保证系统数据的安全性,一般前后台之间的数据访问会有授权与验证,这里的Token机制相对于Cookie支持跨域访问,在RESTful API里面,验证一般可以使用POST请求来通过验证,使服务 ...