dp

来源:互联网 发布:linux system-auth 编辑:程序博客网 时间:2024/06/01 07:28

dp啊..本蒟蒻从初二学到现在都没学好的东西啊QAQ
动态规划(dynamic programming)概念自行百度。【23333
持续更(lan)新(wei)中。
然后就先做这么点总结吧,留的坑再慢慢填上来emmm
逻辑很混乱…各位凑合着看吧。

由于本人语文实在太差,词不达意,这篇可能会讲得非常奇怪,请做好心理准备!


0.dp的一般思路

1.将原问题分解为子问题
2.确定状态
3.确定初始状态(边界状态)的值
4.确定状态转移方程

0.能用dp解决的问题的特点

1.问题具有最优子结构性质

如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质。

2.无后效性

当前的若干个状态值一旦确定,则此后过程的演变就只和这若干个状态的值有关,和之前是采取哪种手段或经过哪条路径演变到当前的这若干个状态,没有关系。


1.数字金字塔

考虑在下面被显示的数字金字塔。 写一个程序来计算从最高点开始在底部任意处结束的路径经过数字的和的最大。每一步可以走到左下方的点也可以到达右下方的点。

7
3 8
8 1 0
2 7 4 4
4 5 2 6 5

在上面的样例中,从7 到 3 到 8 到 7 到 5 的路径产生了最大和:30。
数据小的时候,dfs当然是可以的。

#include<cstdio>#include<algorithm>#define maxn 1005using namespace std;int n,a[maxn][maxn];int dfs(int i,int j){    if(i==n) return a[i][j];    return max(dfs(i+1,j),dfs(i+1,j+1))+a[i][j];}int main(){    scanf("%d",&n);    for(int i=1;i<=n;i++)        for(int j=1;j<=i;j++)            scanf("%d",&a[i][j]);    printf("%d",dfs(1,1));}

但是数据大起来就直接TLE了。因为有很多节点被重复遍历然后就GG了。
然后,记忆化搜索!这样搜索出来的节点就只会调用1次了吧,时间复杂度强行降低。
准确的说,从O(2n)到了O(n2)

#include<cstdio>#include<cstring>#include<algorithm>#define maxn 1005using namespace std;int n,a[maxn][maxn],memo[maxn][maxn];int dfs(int i,int j){    if(memo[i][j]>=0) return memo[i][j];    if(i==n) return memo[i][j]=a[i][j];    return memo[i][j]=max(dfs(i+1,j),dfs(i+1,j+1))+a[i][j];}int main(){    scanf("%d",&n);    memset(memo,-1,sizeof(memo));    for(int i=1;i<=n;i++)        for(int j=1;j<=i;j++)            scanf("%d",&a[i][j]);    printf("%d",dfs(1,1));}

最后就是递推了。预处理出最后一行,然后从下往上推。
//这个dp数组可以不要的说

#include<cstdio>#include<cstring>#include<algorithm>#define maxn 1005using namespace std;int n,a[maxn][maxn],dp[maxn][maxn];int main(){    scanf("%d",&n);    for(int i=1;i<=n;i++)        for(int j=1;j<=i;j++)            scanf("%d",&a[i][j]);    for(int i=1;i<=n;i++) dp[n][i]=a[n][i];    for(int i=n-1;i>0;i--)        for(int j=1;j<=i;j++)            dp[i][j]=max(dp[i+1][j],dp[i+1][j+1])+a[i][j];    printf("%d",dp[1][1]);}

2.背包

给定背包的容量m、物品个数n、物体的重量w[i]和价值v[i],求可以装下的最大的价值。
据我所知有01背包、完全背包和多重背包。
可以灵活变化。
时间:O(mn)

2.1.01背包

顾名思义,就是一种东西只能装一个的背包问题。
考虑每个物品装还是不装,f[i][j]为前i个物品,容量为j时的最大价值,可以得到以下递推式:f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+v[i])。
答案是f[n][m]。
如果开二维数组空间会炸怎么办?
显然(其实是我不能说的很清楚),可以看出这个状态只和前面的状态有关,可以把数组变成一维,即:

for(int i=1;i<=n;i++)    for(int j=m;j>=w[i];j--)        f[j]=max(f[j],f[j-w[i]]+v[i]);

第二层循环的顺序非常重要!
题目(未完待续):

2.2.完全背包

一种东西有无限个。
如果你打算在那两层循环中再枚举一次物品的个数就坐等TLE吧。
这里先给代码吧= =

for(int i=1;i<=n;i++)    for(int j=w[i];j<=m;j++)        f[j]=max(f[j],f[j-w[i]]+v[i]);

可以看出区别就在于第二层循环的顺序。
这很重要然而我就是说不清楚啊QAQ
题目(未完待续):

2.3.多重背包

一种东西不止一个也不一定是无限个。
还是那句话,如果你打算在那两层循环中再枚举一次物品的个数就坐等TLE吧。
一种比较靠谱的方法就是拆成1,2,4,…再用01背包解决。

int x,t=1;while(t*2-1<x){    a[++len]=t*i;    t*=2;}a[++len]=(x-t+1)*i;

题目(未完待续):


3.最长上升子序列

给定一个整数序列A1A2A3….An。求它的一个递增子序列,使子序列的元素个数尽量多,元素不一定要求连续。
考虑以第i个数结尾的最长上升子序列,枚举第i个数之前的所有数,那么它的长度就是以之前小于第i个数的数结尾的最长上升子序列的长度加1。
即:dp[i]=max{dp[j]}+1.(j<i且a[j]<a[i])。
答案是所有的当中最大的。
最长下降/不上升/不下降子序列同理。
时间:O(n2)
题目(未完待续):


4.最长公共子序列

一个给定序列的子序列是在该序列中删去若干元素(也可以不删去)后得到的序列。例如Z=”BCDB” 就是X=”ABCBDAB”的一个子序列,而Z=”CBBD”则不是X的子序列。 给定三个序列 X,Y和Z,如果Z既是X的一个子序列又是Y的一个子串,则称Z是X和Y的公共子序列。
例如X=”ABCBDAB”,Y=BDCABA”,则序列”BCA”即为X和Y的一个公共子序列,但不是X和Y的最长公共子序列(LCS),因为还有比它更长的公共子序列”BCBA”。事实上”BCBA”是X和Y的一个LCS ,”BDAB”也是一个LCS。
现输入两个序列X和Y,要求出X和Y的最长公共子序列。
设f[i][j]为X中前i个元素与Y中前j个元素的最长公共子序列的长度。
那么分2种情况。
X[i]=Y[j]时,f[i][j]=f[i-1][j-1]+1.
X[i]≠Y[j]时,f[i][j]=max(f[i-1][j],f[i][j-1]).
题目(未完待续):


5.资源分配(是叫这个吗我不太确定)

n个物品分配有k个物品(这是什么鬼),求出最优的方案。
思路:前i个物品分配有j个物品,则考虑第j个物品放在第1,2,…,i-1个物品之后。
那么,一般就是:dp[i][j]=max{dp[k][j-1]+a[k+1][i]},1≤k≤i-1.
题目(未完待续):


6.区间

就是在一个区间里面要解决一些奇奇怪怪的问题。
思路:dp[i][i+len]表示起点为i长度为len的区间中的最优解。
然后我也不知道怎么描述了!!
题目(未完待续):


然而,dp怎么可能是靠这几个模型就可以做出来的呢?
dp又岂止这么一点模型?
还得好好学啊emmm。