笔者初识dp还是度娘引见的(辣鸡的学校没有algorithm~)
当下是个资源丰富的时代,基本上各种书上面都有介绍动态规划的概念(然而从来没看懂过...)
记得当初写过一道整数划分的题目,TLE没过,同级的一哥们发了一份代码,一个循环~~~
当时我的一脸感激,然而内心是崩溃的: What FUCK This !?
个人觉得认识dp之前你应该先知道: 递推 , 搜索 , 记忆化搜索 , 分治;
与之对应的题目:
1. 超级楼梯(fibonacci) ; 危险的组合(f[i]=f[i-4]+2*f[i-1]) ; RPG难题 ;
2. 可重全排列 ; 迷宫最短路 ;
3. 角谷猜想(加强版的)
4. 第k小的数; 逆序对 ; a^b%m(快速幂的那种)
...(如果都可以独立过一遍了)N天后:卧槽,太水了!
Ok continue....
其实动态规划就是一种排好顺序的记忆化搜索! 对建模影响比较大的是对数的理解;
它融搜索分治递推的思想于一体,然后看起来比较操蛋
看一个递推的,全错位排列:
题目是说,n封信和n个信封,编号1-n,问:所有信装错的方案数f(n);
(ps:机灵一点的百度了一个公式,秒出答案了,老实的人还在看题解.....)
对于第i份信,如果和它前面的某封(设为x)恰好装反,那么f(i) = f(i-2) * (i-1);
如果没有恰好:我们把i信看成装在x里是对的,那么f(i)=f(i-1)*(i-1);
巧的是这两种情况刚好互斥~
#include<cstdio>
long long a[100];
int main(){
int n;
scanf("%d",&n);
a[1]=0;a[2]=1;a[3]=2;
for(int i=4;i<=n;i++)
a[i]=(i-1)*(a[i-1]+a[i-2]);
printf("%lld\n",a[n]);
return 0;
}
这TM和动规有什么关系吗?呵呵...你说呢?
别问了,问多了烧脑子~
直接看几个模型就Over吧:
1. 位置记忆
最长不下降子序列:(这个太明显的记忆化~)
input:
10
12 13 15 12 14 19 21 21 20 17
output:
6
#include<cstdio>
#include<algorithm>
int n,a[101],dp[101],ans=0;
int main(){
scanf("%d",&n);
for(int i=0;i<n;i++)
scanf("%d",a+i);
dp[0]=1;
for(int i=1;i<n;i++){
for(int j=i-1;j>=0;j--)
if(a[i]>=a[j])
dp[i]=std::max(dp[i],dp[j]+1);
ans=(ans>dp[i]?ans:dp[i]);
}
printf("%d\n",ans);
return 0;
}
2. 比较取优
0-1背包(可取部分的那种直接贪单位质量最大:v[i]/c[i]降排即可对吧?)
输入n表示n个物品,vol表示体积,w[i]是重量,c[i]是体积,求在vol体积下最重可达多重?
input:
7 15
4 6
3 9
2 8
7 7
12 18
6 11
5 7
output:
34
按取第几件物品来划分阶段: 能取就取最优(dp[i-1][j]表示没取,dp[i-1][j-c[i]]+w[i]就是取i物品),不能取就不取
#include<cstdio>
#include<algorithm>
int dp[101][1010],c[101],w[101],n,vol;
int main(){
scanf("%d%d",&n,&vol);
for(int i=0;i<n;i++)
scanf("%d%d",c+i,w+i);
for(int i=0;i<n;i++)
for(int j=0;j<=vol;j++){
if(j>=c[i]&&i>0)
dp[i][j]=std::max(dp[i-1][j],dp[i-1][j-c[i]]+w[i]);
else if(i==0)dp[i][j]=w[i];
else dp[i][j]=dp[i-1][j];
}
printf("%d\n",dp[n-1][vol]);
return 0;
}
/*
dp[i][j]表示对第i件物品,在j容量的情况下最重重量
*/
3. 累加状态
过河卒:(马拦过河卒:象棋那种马)
对方一匹马,你的卒子过河后只能往右或者往下(这让我想起了T步...),问从(0,0)到(n,m)的路线多少种
input:
6 6 3 2
output:
17
#include<cstdio>
#include<cstring>
int dp[101][101],n,m,mx,my;
void _set(int x,int y){
if(x<0||x>n||y<0||y>m)return ;
dp[x][y]=0;
}
int main(){
scanf("%d%d%d%d",&n,&m,&mx,&my);
memset(dp,-1,sizeof(dp));
_set(mx-2,my-1);_set(mx-2,my+1);_set(mx-1,my-2);
_set(mx-1,my+2);_set(mx,my);_set(mx+1,my-2);
_set(mx+1,my+2);_set(mx+2,my-1);_set(mx+2,my+1);
for(int i=0,c=1;i<=m;i++){if(!dp[0][i])c=0;dp[0][i]=c;}
for(int i=0,c=1;i<=n;i++){if(!dp[i][0])c=0;dp[i][0]=c;}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(dp[i][j])dp[i][j]=dp[i-1][j]+dp[i][j-1];
printf("%d\n",dp[n][m]);
return 0;
}
/*这个阶段一太明显了dp[i][j]=dp[i-1][j]+dp[i][j-1]*/
4. 累加分类
整数划分(这个用的上的地方太多了,只要脑洞够大)
老生常谈,不多说直接贴代码
#include<cstdio>
#include<algorithm>
int dp[101][101],n,k;//dp[i][j]表示把j分成i份
int main(){
scanf("%d%d",&n,&k);
for(int i=0;i<=n;i++)dp[1][i]=1;
for(int i=2;i<=k;i++)
for(int j=0;j<=n;j++)
if(j>=i)dp[i][j]=dp[i-1][j]+dp[i][j-i];
else dp[i][j]=dp[i-1][j];
printf("%d\n",dp[k][n]);
return 0;
}
5. 插入比较
石子归并(老题目了,经典区间规划,就像Floyd一样)
不做无用复述...就是说n堆石头,只能和相邻的合并(首尾不可直接合并),合并的代价是a的重量加b的重量,求最优合并使得代价最小
#include<cstdio>
#include<cstring>
#include<algorithm>
int dp[101][101],n,w[101];
int main(){
scanf("%d",&n);
memset(dp,127,sizeof(dp));
for(int i=1;i<=n;i++){
scanf("%d",w+i);
w[i]+=w[i-1];
dp[i][i]=0;
}
for(int i=n-1;i>0;i--)//起点,
for(int j=i+1;j<=n;j++)//终点
for(int k=i;k<j;k++)//插入
dp[i][j]=std::min(dp[i][j],dp[i][k]+dp[k+1][j]-w[i-1]+w[j]);
printf("%d\n",dp[1][n]);
return 0;
}
6. 累加相关类别
还有一些常见的(蓝桥杯的牌型种数,K好数之类的)
牌型种数:13张牌,不考虑花色和顺序,值看点数,求从52张中抽13张的方案数:
#include<cstdio>
int dp[15][15];
int main(){
dp[1][0]=dp[1][1]=dp[1][2]=dp[1][3]=dp[1][4]=1; //f(i,j)表示有i种排,发你j张的可能方案
for(int i=2;i<=13;i++)
for(int j=0;j<=13;j++)
for(int k=0;k<=4&&k<=j;k++)//看成排序,对于第j张取0-4
dp[i][j]+=dp[i-1][j-k];
printf("%d",dp[13][13]);
return 0;
}
题是无限的,模型只会这么多(所以常爆零...)
总之:
搜索是要找关键字界限;
动规是要找决策阶段,然后把要用的先算出来就ok(然而初始化是最恶心的,调试倒是可以取小数据手算~)
找界限,找阶段,找转移,排计算顺序,
恩,略懂~