动态规划

引例:

斐波那契数列:
边界条件:f0=0;

f1=1;

能够直接被求出值的状态

不需要计算其他斐波那契数列的值直接可以得到结果;

转移方程:fn=fn-1+fn-2如何用已有状态求出未知状态

前几项:0,1,1,2,3,5,8,13……

状态:f1,f2,f3……fn;(要求的未知的量)

DAG<=>无后效性??(暂时不用管什么东西)

通项公式:

实现DP

法1:记忆化搜索(一般来说用不上qwq)

会多开一个记录是否算过的数组,故空间会比下面两种大一点

法2:顺着推:用自己去推别人

法3:倒着推:用别人更新自己

#include<iostream>
#include<cstdio>
using namspace std; int n,f[];
/*斐波那契数列:倒着推
int main(){
cin>>n;
f[0]=0;
f[1]=1;
for(int a=2;a<n;a++)
{
f[a]=f[a-1]+f[a-2];
}
cout<<f[n]<<endl;
}*/
/*斐波那契数列:顺着推
int main(){
cin>>n;
f[0]=0;
f[1]=1;
for(int a=0;a<n;a++){
f[a+1]+=f[a];
f[a+2]+=f[a];
}
cout<<f[n]<<endl;
} */
/*搜索 O(f(n)) 与斐波那契数列第n项大小成正比 约为1.6^n
int dfs(int n){
if(n==0) return 0;
if(n==1) return 1;
return dfs(n-1)+dfs(n-2);
} int main(){
cin>>n;
cout<<dfs(n)<<endl; return 0
} */
/*记忆化搜索 O(n)
在计算过程中,保证f0~fn每个数只被算一次 bool suan_le_mei[n]; int dfs(int n){
if(n==0) return 0;
if(n==1) return 1;
if(suan_le_mei[n]) return f[n];//判断是否算过 suan_le_mei[n]=true;
f[n]=dfs(n-1)+dfs(n-2); return f[n];
} int main(){
cin>>n;
cout<<dfs(n)<<endl; return 0
}*/

常见DP种类:

数位DP-50%

树形DP-50%

状压DP-50%

区间DP-50%

(有套路^)

其他DP-80%

(无规律^)

考不到的DP:插头DP,博弈论DP,呸概率比较小

常见优化:

单调性优化

矩阵乘法优化

其他优化

放弃学习优化

看的一懵一懵的

数位DP

什么叫做数位DP?

按照数字的位数划分转移阶段

转移方式:枚举下一位数字填什么

限制条件:数位的上下界要求

读入两个正整数l,r,问从l~r有多少个数?

显然ans=r-l+1;

但是,我们的钟神拒绝平常,他要与众不同,他要用数位DP做qwq

第一步:转化:[0,r]数的个数-[0,l-1]的数的个数=>转化成解决[0,x]有多少数=>有多少个v使得0<=v<=x;

x=>xn xn-1 xn-2……x0

v=>vn vn-1vn-2……v0(至多n位)给每位填上0~9的数,看有多少种方案满足v<=x

从最高位=>最低位

举个栗子:填vn-3时,前面的都填好了

那么v的前面的三位必须保证小于等于x的前三位,那么为了使v<=x,则需要:

分两种情况:1.x的前三位大于v的前三位=>vn-3可以填任何数

2.x的前三位等于v的前三位=>0~xn-3

f[i][j(0/1)] 代表这种情况的方案数

i:已经填好了第i位,j=>0/1

j=0=>xnxn-1……xi>vnvn-1……vi

j=1=>xnxn-1……xi=vnvn-1……vi

转移:枚举第i-1位填什么

填第i位,从i+1位转移过来.

边界:第n+1位(相等且没有数)f[n+1][1]=1;(都填0)

#include<iostream>

using namespace std;

int l,r,z[];
int f[][]; int solve(int x){
int n=;
while(x){//求出x的每一位(最后有n位)(从0下标开始)
z[n]=x%;
x/=;
n++;
}
n--;
memset(f,,sizeof(f));//需要做两次DP,故要把数组清空 f[n+][]=;//边界条件:第n+1位显然都填0,所以相等的方案数为1
//不相等的方案数为0 for(int a=n;a>=;a--)//枚举要填的第a位
{
for(int b=;b<=;b++)//考虑相等还是小于分两种讨论qwq
if(b==){//x>v
for(int c=;c<=;c++)
f[a][]+=f[a+][b];
}
else {
for(int c=;c<=z[a];c++){
if(c==z[a])f[a][]+=f[a+][b]//c==z[a],代表填上这个数后v=x
else f[a][]+=f[a+][b];//c!=z[a],代表填上这个数后v<x
}
}
}
return f[][]+f[][];
} int main(){ cin>>l>>r; cout<<solve(r)-solve(l-)<<endl; return ;
}

problem2:

求[l,r]中的数的数位之和

#include<iostream>
#include<cstring> using namespace std; int l,r,z[];
int f[][];
int g[][];//ij表达与f相同 int solve(int x){
int n=;
while(x){//求出x的每一位(最后有n位)(从0下标开始)
z[n]=x%;
x/=;
n++;
}
n--;
memset(f,,sizeof(f));//需要做两次DP,故要把数组清空
memset(g,,sizeof(g)); f[n+][]=;//边界条件:第n+1位显然都填0,所以相等的方案数为1,不相等的方案数为0
g[n+][]=; for(int a=n;a>=;a--)//枚举要填的第a位
{
for(int b=;b<=;b++)//考虑相等还是小于分两种讨论qwq
if(b==){//x>v
for(int c=;c<=;c++){
f[a][]+=f[a+][b];
g[a][]+=g[a+][b]+f[a+][b]*c;//先加上前a+1位的和,然后对于第a位,可以填0~9任一,那么每填一个数c,每一种方案的和都+c,故要用方案数*c
}
}
else {
for(int c=;c<=z[a];c++){
if(c==z[a]){
f[a][]+=f[a+][b];
g[a][]+=g[a+][b]+f[a+][b]*c;
}//c==z[a],代表填上这个数后v=x
else {
f[a][]+=f[a+][b];//c!=z[a],代表填上这个数后v<x
g[a][]+=g[a+][b]+f[a+][b]*c;
}
}
}
}
//return f[0][0]+f[0][1];
return g[][]+g[][];
} int main(){ cin>>l>>r; cout<<solve(r)-solve(l-)<<endl; return ;
}

problem3:

求在[l,r]中的满足相邻两个数字之差至少为2的数有多少个

多一维条件,多一个状态。

f[i][j][k]已经填好了第i位;j:0/1表示</=;k:第i位填了k

保证第i位和第i+1位的数字大小差至少2;

windy数qwq

树形DP:

什么是树形DP:

按照树从根往下或者叶子网上划分阶段

转移方式:集合叶子或者父亲的信息

限制条件:不详

给你一个n个点的数,问有多少个点???闲圈啊qwq

f[i]以i为根的子树中共有多少个点

边界条件:叶节点的f为1;f[叶子]=1;

f[p]=f[p1]+f[p2]+1;

转移方程 f[i]=f[son1]+f[son2]+……+f[sonn]+1

eg:

给我一个n个点的数,求这棵树的直径是多少?

直径:在一棵树上找到两个点,使得这两点间的距离最远。

树路径长度,从根结点到某结点的边数。

考虑一棵子树内的直径qwq

路径长这样:

可以把直径看成从某一点向下走了两条路然后合起来

找两种向下走最长的两条路径

即从一点向下走最长可以走多少和次长可以走多少。

状态定义:f[i][0]i向下最长那条路长度是多少

f[i][1]i乡下的次长的那条路的长度

转移方程:(n个儿子)

f[p][0]=max(f[son1][0],f[son2][0]……,f[sonn][0])+1;

假设最大的是son2,那么显然我们不能再走son2了

f[p][1]=max(f[son1][0],f[son3][0],……f[sonn][0])+1;

ans=max(f[i][0]+f[i][1]);(枚举所有的i找最大的一个)

区间DP:

满足:

合并相邻的

把所有的合并成一个东西

的为区间DP;

eg:合并石子

n堆石头,a1,a2……an,可以合并相邻的两堆石子,合并的代价:合并的这两堆的石头数之和。(合并n-1次qwq)现在把这n堆石头合并为1堆,使代价最小。

状态:f[l][r]代表把第l堆石子到第r堆石子合并为一堆石子的最小代价

要求的即为f[1][n]

边界条件:l=r时答案为0;即f[i][i]=0;

转移方程:f[l][r]=min(f[l][p]+f[p+1][r]+sum[l][r])

合并过程并没有改变石头的顺序,最后一定为把两堆合并为一堆,那么一定可以找到一条分界线p,左边为al~ap右边为ap+1,~ar

左边合并的最小代价即为f[l][p],右侧为f[p+1][r];

枚举一个点p把左右分别合并成一堆,然后再合并成一堆

复杂度:O(n3)(n的极限约为100~200左右)

石子合并:洛谷p1880

处理第1堆和第n堆相邻的情况

很自然的想法:

最后的答案可能不是f[1][n]

an和a1相邻,a1,a2不相邻的情况:f[2][n+1]

再加一个a2:

继续向后面加:(在原来序列基础上补上一个相同的序列)

取min

原理:

对于一个环形排序的n堆物品,要把他们合并为一堆,需要合并n-1次,那么有一条边始终没有用到,我们可以看是断开的,也就是说,我们可以不看它,把长度延长为原来的两倍即为枚举要断开的边是哪一条


下午

状态压缩DP

什么叫状压DP:

按照选取集合的状态划分转移阶段

转移方式:枚举下一个要选取的物品

限制条件:不详

N个点:(x1,y1)(x2,y2)……(xN,yN

从一号点出发,保证每个点都至少走一次,使得走过的路径的长度之和最短

TSP问题(旅行商问题)非常经典的NP-hard问题(想解决掉这种问题,至少为2n,n在指数上)qwq

一个点有没有必要走两次???没有=>最优情况下每个点只需去一次

应该怎么走???直接拉一条线段过去<=>两点间线段最短

已走:1 2 5

未走:3 4 6

现在的位置在5号点,那么接下来的状态:

如何把集合表示到代码里面去:(如何用一个数代表一个集合)构造一个n位的二进制数,

<=>{1,4,5}

转成10进制=>25

每个元素只可能 在/不在 集合中两种情况,第i位在集合中为1,不在集合中为0

设计这样一个DP方法:

f[s][i]

s:n位的二进制数,已经走到过的点

:{1,2,4,6}说明已经走过1,2,4,6点,对应二进制=>101011

i:现在停留在i点

初始化:f[1][1]=0;

转移:

从i点到j点,j属于1~n;

并且要判断j∉s

f[s∪{j}][j]<=f[s][i]+dis[i][j];

p1171

#include<cstdio>
#include<iostream> using namespace std; const int manx=; int n;
double f[<<maxn][maxn];//f[2的n次方][n]; double dis(int i,int j){
return sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]));
} int main(){ cin>>n;
for(int a=;a<n;a++)//二进制最低位是第0位
cin>>x[a]>>y[a];
for(int a=;a<(<<n);a++)
for(int b=;b<n;b++)
f[a][b]=1e+;
f[][]=;//初始化
//转移:较小的s=>较大的s,从小到大枚举s for(int s=;s<(<<n);s++)
for(int i=;i<n;i++)
if(((s>>i)&)==)
for(int j=;j<n;j++)
if(((s>>j)&)==)
f[s|(<<j)][j]=min(f[s|(<<j)][j],f[s][i]+dfs(i,j));
double ans=le+;
for(int a=;a<n;a++)
ans=min(ans,f[(<<n)-][a]); cout<<ans<<endl;
}

状压DP:

时间复杂度:O(n2*2n) 空间复杂度:O(2n*n)能接受的数据范围:n<=20(大约是,有时候可以达到22)

一般机子1s可以跑107~109看机子不同吧qwq

一些数据范围推复杂度和算法:

n≤12=>暴搜

n≤20(22)=>状压

n≤32  =>放弃

n≤50  =>放弃

n≤100 =>n3

n≤1000 =>n2

n≤105=>数据结构,线段树

n≤106=>线性的

n>106=>考虑O(1)的算法

在这普通的一天,我穿着普通的鞋,很普通地来到这普通的酒店,打开普通的电脑,找点普通的感觉,学一点我最爱的普通DP

普通DP

题目多一个条件=>多一个维度

first:

边界条件:

第一行和第一列全部为1:f[i][1]=f[1][j]=1;

转移:f[i][j]=f[i-1][j]+f[i][j-1];

上次zt钟神也讲的一个东西:

n行m列:从左上走到右下的方案数:Cn+m-2n-1

数字三角形:

洛谷p1216

每次可以向下或右下走。

这条路径的权值:这条路上所有数的和

求最大值

状态f[i][j]表示在走到(i,j)这个点最大的权值是多少;

边界条件:f[1][1]=a[1][1];

转移方程式:f[i][j]=max(f[i-1][j-1],f[i-1][j])+a[i][j];

数字三角形改:

从a[1][1]向下或向右下走,使得路径权值mod m后的值最大

n,m<=100;

感性理解:

为什么不给大一点???

1.给大了做不了    m可能也是一个维度

2.出题人懒得给大数据(wz啊)

边界:f[1][1][a[1][1]]=true;

状态:f[i][j][k]走到i行j列%m==k是可能or不可能

转移:f[i][j][k]=f[i-1][j-1][k-a[i][j]]orf[i-1][j][k-a[i][j]]

注意k-a[i][j]可能会为负,故要取模

ans最后一行中可能出现的k中最大的

codevs 上的一道题

下一个问题:

最长上升子序列

不细讲了大家都做过qwq:

给定一个序列a1,a2,a3……an

找出一个序列b1,b2……bk使得:ab1<ab2<……abk

f[i]表示找一个以i号点结尾的最长上升子序列长度

转移:找一个j使得1<=j<i,aj<ai枚举f[i]=max f[j]+1;

复杂度O(n2

加强一下:数据范围n<=105

线段树qwq没学好啊

假设v=max(a1,a2,a3……,an)

建一棵大小为n的线段树(按值来做左右划分)

线段树:区间询问最大值&单点修改???

有点特殊的DP:背包问题

背包九讲

最基本背包问题:有n个物品,第i个物品价值为w[i],体积为v[i],现在有一个背包大小为m,在不超过背包容积情况下,价值最大。

01背包

状态定义:f[i][j]把前面i个物品是否放进背包已经考虑完了,背包中已经使用了j的体积的最大价值。

转移:如果不放进背包=>f[i][j]=>f[i+1][j] 0转移

如果放进背包=>f[i][j] +w[i+1]=>f[i+1][j+v[i+1]] 1转移

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

无穷背包(完全背包)

有n个物品,第i个物品价值为w[i],体积为v[i],每种物品都有无数个,问同上;

未优化:

转移方程:f[i][j]=max(f[i-1][j],f[i][j-v[i]]+w[i])O(nm)

存在转移链:

f[i][j]<=f[i][j-v[i]]<=f[i][j-2v[i]];

有限背包:

做法:枚举每个物品要用多少个(同无穷背包未优化的操作)

eg:求在[l,r]中满足各位数字之积为k的数有多少个

l,r<=1018

再加一维:f[i][j][k]填到i位,j=0  => “<”,j=1  => “=”,乘积为k;

k:max:918会炸,因为0~9中质因数只有2,3,5,7,故k=2a*3b*5c*7d

拆维度qwq拆的我一脸懵逼:

有的时候维度多并不代表会炸掉,有时反而或许可以降低复杂度qwq。

很神奇,你居然看到了最后,tql

那么接下来就是钟神语录了(算是看到最后的小彩蛋吧qwq)

"虽然我不知道你说的是什么,但肯定是错的"

“这个题我是从vijos看到的,不知道这个oj还活着没有”

“当你发现你这样做做不出来时,加一个维度,还做不出来,再加一个维度QWQ”

【五一qbxt】day3 动态规划的更多相关文章

  1. 【五一qbxt】day7-2 选择客栈

    停更20天祭qwq(因为去准备推荐生考试了一直在自习qwq) [noip2011选择客栈] 这道题的前置知识是DP,可以参考=>[五一qbxt]day3 动态规划 鬼知道我写的是什么emm 这道 ...

  2. qbxt Day3 on 2019-8-18

    qbxt Day3 on 2019-8-18 一.基础数论 1.进制转换 进制转换是一个非常简单且基础的问题. 也许我们只需要...Emmm... 列个式子就好了鸭 设\(k\)进制数每一位上是\(a ...

  3. qbxt五一数学Day3

    目录 1. 组合数取模 1. \(n,m\le 200\),\(p\) 任意 2. \(n,m\le 10^6\),\(p\ge 10^9\) 素数 3. \(n,m\le 10^6\),\(p\le ...

  4. qbxt Day3 其余题

    例题1 noiopenjudge9277 给出在最底层的木头的个数,问有多少种堆放木头的方式,当然你的堆放方式不能让木头掉下来. 在堆放的时候木头必须互相挨着在一起. 设dp[i]表示多少个log(原 ...

  5. 【五一qbxt】test1

    (不知道为什么居然爆零了qwq) (全员爆零诶,最高分10分???还是rand出来的???) 我freopen写错了????自闭了 不行不行再写一遍freopen加深印象,不能再写错了 freopen ...

  6. 【五一qbxt】test2

    又犯了一些迷之错误??要不然yy鼠标就是我的了 1.Superman: 小姐姐的题解:直接用set模拟即可emmmm 里面有很多指针啊,乱七八糟的,不会qwq,先看看我的大模拟吧: #include& ...

  7. 【五一qbxt】day7-1 引水入城

    [noip2010 洛谷p1514]引水入城 Before: 线段覆盖问题#1:(我们所需要的) 一个区间,若干条线段,现在求最少多少条线段覆盖满整个区间 区间长度8,可选的覆盖线段[2,6],[1, ...

  8. 【五一qbxt】day6 OI中的stl

    from:why 很多很多part…… 1.pair: 相当于把两个变量放在一起: #include<utility> using namespace std; pair<TypeN ...

  9. 【五一qbxt】day5 图论

    图论 学好图论的基础: 必须意识到图论hendanteng xuehuifangqi(雾 图 G = (V,E) 一般来说,图的存储难度主要在记录边的信息 无向图的存储中,只需要将一条无向边拆成两条即 ...

随机推荐

  1. 解决IDEA Initialization error 'https://start.spring.io'

    IDEA Initialization error  'https://start.spring.io'     弹出一个error窗口    就是不能连接https://start.spring.i ...

  2. .NET面试题系列(十九)Socket网络异常类型

    序言 资料 异常测试之Socket网络异常

  3. asp.net 如何实现大文件断点上传功能?

    之前仿造uploadify写了一个HTML5版的文件上传插件,没看过的朋友可以点此先看一下~得到了不少朋友的好评,我自己也用在了项目中,不论是用户头像上传,还是各种媒体文件的上传,以及各种个性的业务需 ...

  4. BZOJ 5028 小z的加油站

    bzoj链接 Time limit 10000 ms Memory limit 262144 kB OS Linux 感想 树上动态gcd的第二题也好了. [x] BZOJ 2257 [JSOI200 ...

  5. Oracle生成ASH报告

    1.ASH (Active SessionHistory) ASH以V$SESSION为基础,每秒采样一次,记录活动会话等待的事件.不活动的会话不会采样,采样工作由新引入的后台进程MMNL来完成. v ...

  6. 使用http-server在本地搭建一个HTTP服务器

    安装http-server 在控制台中输入 npm install http-server -g 进行全局安装 共享资源 进入需要共享资源的目录,比如 D:\,然后在控制台中输入 http-serve ...

  7. 读取hibernate.cfg.xml文件

    new Configuration().configure().buildSessionFactory() new Configuration()默认是读取hibernate.properties 所 ...

  8. JAVA周二学习总结

    第一周我感觉我学到了不少东西,其中有上学期C语言学的不好的原因,因为不想再挂科就有认真的在听. 学到的东西有JAVA的基本书写模式自己懂了,还在课堂上弄会了交换数据的方法,还有运算的算法,另外还有数据 ...

  9. Deepin 系统下设置Apache虚拟主机站点

    1,在 var/www/html 文件夹中创建一个文件夹,命名为 a ,用来作为我们的PHP项目文件夹. 2,找到 etc/apache2/sites-available 文件夹,在文件夹中创建一个站 ...

  10. Mybaits多个参数的传递

    今天介绍是多个参数传递到映射xml,进行CURD操作 一.使用参数映射的方法进行传递 1在接口写对应的方法 public interface EmployeeMapper { public Emplo ...