动态规划初步
Posted wyctstf
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了动态规划初步相关的知识,希望对你有一定的参考价值。
什么是动态规划:
通常用于求解某种具有最优性质的问题
有许多可行解,须从中挑出一个最优的
基本思想是将问题分解成若干个子问题,先解决子问题,合并后得到原问题的解
但是单纯分解,产生子问题太多,有些被重复计算
故可以使用一个表记录子问题结果(动规的基本思路)
典型例题:
挖金矿(01背包问题)
问题:
有一个国家,所有的国民都非常老实憨厚,某天他们在自己的国家发现了十座金矿,并且这十座金矿在地图上排成一条直线,国王知道这个消息后非常高兴,他希望能够把这些金子都挖出来造福国民,首先他把这些金矿按照在地图上的位置从西至东进行编号,依次为1、2、3、4、5、6、7、8、9、10,然后他命令他的手下去对每一座金矿进行勘测,以便知道挖取每一座金矿需要多少人力以及每座金矿能够挖出多少金子,然后动员国民都来挖金子。
已知条件1:
挖每一座金矿需要的人数是固定的,多一个人少一个人都不行。国王知道每个金矿各需要多少人手,金矿i需要的人数为p[i]
已知条件2:
每一座金矿所挖出来的金子数是固定的,当第i座金矿有p[i]人去挖的话,就一定能恰好挖出g[i]个金子。否则一个金子都挖不出来。
已知条件3:
开采一座金矿的人完成开采工作后,他们不会再次去开采其它金矿,因此一个人最多只能使用一次。
已知条件4:
国王在全国范围内仅招募到了10000名愿意为了国家去挖金子的人,因此这些人可能不够把所有的金子都挖出来,但是国王希望挖到的金子越多越好。
已知条件5:
这个国家的每一个人都很老实(包括国王),不会私吞任何金子,也不会弄虚作假,不会说谎话
已知条件6:
国王只需要知道最多可以挖出多少金子即可,而不用关心哪些金矿挖哪些金矿不挖
***
相关概念:
子问题:
拆分相对大问题后得到的相对小问题(有点像历史宗法制的大宗和小宗?)
最优子结构(见名知意)
重叠子结构:
子问题相同的情况,这是造就DP高效的重要原因(避免重复处理)
边界:
程序满足的条件:例如挖金矿中的人数限制,01背包问题中的背包承载量限制等等
子问题独立:
备忘录方法:
可以为每个字问题建立一个记录项,第一次计算时做记录,以后直接读取
子问题互相独立
****
基本模型:
阶段和阶段变量
状态和状态变量
决策、决策变量和决策允许集合
策略和最优策略
状态转移方程
基本原理:
最优化原理:子问题最优解构成问题最优解
无后效性原则:未来不受过去影响
重叠子问题:记录出现过的子问题,优化
感受状态转移方程:
[例题] P1216 [IOI1994][USACO1.5]数字三角形 Number Triangles
#include<iostream>
using namespace std;
int n;
int a[1005][1005]={0}, //边界条件
f[1005][1005]={0}; //边界条件
int main()
{
cin>>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++) //外层循环控制阶段,i---阶段变量
for(int j=1;j<=i;j++) //内层循环控制状态,j---状态变量
{
if(i==1&&j==1) f[i][j]=a[i][j];
else f[i][j]=max(f[i-1][j-1],f[i-1][j])+a[i][j];
}
int ans=0;
for(int i=1;i<=n;i++)
ans=max(ans,f[n][i]); //在表中搜索,取最大值
cout<<ans<<endl; //输出最优子问题
return 0;
}
最长上升子序列(LIS):
设有n个不相等的整数组成的数列,记为b_1,b_2,...,b_n; 若其中包含了一个b_i1<b_i2<...<b_ie的数列,则称为长度为e的上升子序列。给出一个数列,要求用程序求出其LIS的长度。
解这道题的基本思想就是用一个表记录从第一个点到a[i]个点的最大子序列长度,对应长度为e的数列,就用长度为e的表,下边来分析做这题时的几个模型;
既然要对数列中的每个数进行分析,那么首先要有个循环嵌套,外层控制正在分析的数(设循环变量为i),内层分析以这个数为结尾的数列的最长上升子序列(设循环变量为j),则i为阶段变量,j为状态变量。
得出LIS的状态转移方程式为f(i) = max{ f[j] + 1 , f[i] || j < i , b[j] < b[i] }
程序实现:
#include<iostream>
using namespace std;
int a[100010]; //全局数组
int LIS(int x)
{
int num[100010];
for(int i=1; i<=x; i++)
{
num[i]=1;
for(int j=1; j<i; j++)
if( a[j]<a[i] && num[j]+1>num[i] ) //状态转移方程应用
num[i]=num[j]+1; //更新
}
int maxx=0;
for(int i=1; i<=x; i++)
if(maxx<num[i])
maxx=num[i]; //在每个最优子问题的解中找出总问题最优解
return maxx; //返回最优解
}
int main()
{
int n;
cin>>n; //输入子序列长度
for(int i=1; i<=n; i++)
cin>>a[i]; //输入待分析数列
cout<<LIS(n);
}
实战:
P2782 友好城市
蒟蒻第一次做的时候毫无悬念的TLE了,于是需要用到的O(nlogn)LIS,所以我们现在就要吧O(n^2)的LIS改成O(nlogn)的复杂度,因为子问题不能改,外层循环不能动,我们只能在内层循环动手脚:
利用栈的思想,将数列中的数一个个存入,若数比栈顶元素大,就覆盖上去,若比他小,则用二分查找的办法,替换掉最该被替换的数字(恰好小于存入数字的数),这点某位da lao有详细讲解,也可点击详情查看更多
以下是AC代码:
#include<bits/stdc++.h>
using namespace std;
const int MAX=200010;
int dp[MAX];
struct city
{
int n,s;
} a[MAX];
int cmp(city a,city b)
{
return a.n<b.n;
}
int main()
{
int n,len;
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i].n>>a[i].s;
sort(a+1,a+1+n,cmp);
for(int i=1;i<=n;i++)
if(a[i].s >= dp[len]) dp[++len]=a[i].s; //若大于,则入栈
else
{
int j=lower_bound(dp,dp+len,a[i].s)-dp;
dp[j]=a[i].s;
}
cout<<len<<endl;
}
P1091 合唱队形
思路:题目想让我们将队伍变成一个由最长上升序列和最长不上升序列构成的队伍,类似于一个三角形的模样。计算LIS长度和反LIS长度,,针对每个点计算,取最大的值,总人数减去最优子问题的解就是此题想要的最优解,这不难理解:
#include<bits/stdc++.h>
using namespace std;
int a[110],dp[3][110],n,ans;
int main()
{
cin>>n;
for(int i=1; i<=n; i++) cin>>a[i];
for(int i=1; i<=n; i++)
for(int j=0; j<i; j++)
if(a[i]>a[j]) dp[1][i]=max(dp[1][i],dp[1][j]+1);
for(int i=n; i; i--)
for(int j=n+1; j>i; j--)
if(a[i]>a[j]) dp[2][i]=max(dp[2][i],dp[2][j]+1);
for(int i=1; i<=n; i++) ans=max(dp[1][i]+dp[2][i]-1,ans);
cout<<n-ans<<endl;
}
To be continued . . .
以上是关于动态规划初步的主要内容,如果未能解决你的问题,请参考以下文章