一 .递推问题
1.确定递推状态。(找到一个表达式,可以表示当前状态)
2.找到递推关系(可以利用容斥原理)拆分为C=A+B-A∩B...
例题1:
1.确定递归状态:分析,由于本题头尾元素最关键,所以需要记录头和尾的颜色种类。所以就可以是f[n][i][j] ;n是格子总数,i是头元素的颜色,j是尾元素的。
2.找到递推关系:f[n][i][j]应该等于f[n][i][k](k!=j)(这可以看作是一种按操作步骤的递推问题)
*3. 注意不符合要求的:当i==j时,舍去。
3.递推式的边界条件: f[1][ i][j]=={1/0}当且仅当i==j时为1;
4.如何求解答案。累加f[n][i][j]
例题2:
1.定义状态:f【i】【j】:将数字i分成j份...
二.动态规划问题:
1.确定动规状态:
2.确定状态转移方程:
3.正确性证明:
一些动态规划问题的概念:
阶段:把一个问题分解成若干个相互联系的阶段。
无后效性: 如果得到了某一阶段的值a,那么a的值不受后续状态计算的影响而改变。
决策: 从一个状态到下一个状态的选择称作决策。
例题1:数字三角形问题:
1.f(i,j)表示从顶点走到(i,j)点能获得的最大值。
2.确定递推公式:
f(i,j)=max(f(i-1)(j-1),f(i-1)(j))+f(i)(j)
注意头尾可以扩容或特判。
例题2.0/1背包问题。
1.定义状态 f(i,j)表示用前i种商品,背包重量为j。
2.
在实际代码时候,需要判断j是否小于v[i]的值,如果是的话就不需要比较最大值了(因为根本装不下第i件物品。)
int V,n,v,w,dp[10005];
scanf("%d %d",&n,&V);
for(int i=0;i<n;i++){
scanf("%d %d",&v,&w);
for(int j=V;j<=v;j--){
dp[j]=max(dp[j-v]+w,dp[j]);//怎么理解这行代码?左边的 dp[j]表示dp[i][j],右边的表示dp[i-1][j-v]...正因如此,我们采用逆向刷表,保证右边是更新前的数据)
}
}
printf("%d",dp[V]);
return 0;
例题3:完全背包问题:
int V,n,v,w,dp[10005];
scanf("%d %d",&n,&V);
for(int i=0;i<n;i++){
scanf("%d %d",&v,&w);
for(int j=v;j<=V;j++){
dp[j]=max(dp[j-v]+w,dp[j]);//为什么这里方向改变了?因为看递归式,右边是dp[i][j-v],所以右边要先更新。从前往后刷表。
}
}
printf("%d",dp[V]);
//注,刷表法尤其适合这种i-->i-1的。
例题4:最长上升子序列:
注意一个结论:动态规划问题一定可以求出最优方案。且一般在决策(改变)过程记录。
1.确定动规状态:dp【i】表示以i位置结尾的最长上升子系列。
2:确定状态转移方程:合法的前序序列dp【k】(1<=k<i)最大值+1;
//注意:以下代码会超时,优化算法后续给出。
int num[1000000],up[1000000],pre[1000000],ans=INT_MIN,n,ind;
int main() {
scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%d",&num[i+1]);
}
for(int i=1;i<=n;i++){
up[i]=1;
for(int j=1;j<n;j++){
if(num[i]<=num[j])continue;
if(up[i]<up[j]+1){
pre[i]=j;
up[i]=up[j]+1;
}
}
if(ans<up[i]){
ind=i;
ans=up[i];
}
}
printf("%d\n",ans);
while(ind){
printf("%d->",num[ind]);
ind=pre[ind];
}
return 0;
}
例题5:最长公共子序列:
定义:dp[i][j]表示s1字符串前i位和s2字符串的钱j位最长公共子序列。
#include<bits/stdc++.h>
using namespace std;
#define maxn 1005
char s1[maxn],s2[maxn];int dp[1000][1000];
int main(){
scanf("%s%s",&s1,&s2);
int len1=strlen(s1),len2=strlen(s2);
for(int i=0;i<len1;i++){
for(int j=0;j<len2;j++){
dp[i][j]=max(dp[i][j-1],dp[i-1][j]);
if(s1[i]==s2[j])dp[i][j]=max(dp[i][j],dp[i-1][j-1]+1);
}
}
printf("%d",dp[len1-1][len2-1]);
return 0;
}
例题6:
引入区间dp的概念: 是指以区间长度作为动态规划的状态,由短到长。
从小区间答案更新到大区间的答案。
对于本题:我们分析可以得知,如果我们在k处切一刀,那么他的刀数应该等于(l,k)刀数+(k,r)刀数+1。所以区间是一个不断缩短的过程,故采用区间dp。
例题7:棋盘分割:
//todo