The Fuck dp (优雅的动规)

笔者初识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(然而初始化是最恶心的,调试倒是可以取小数据手算~)

找界限,找阶段,找转移,排计算顺序,

恩,略懂~








   


你可能感兴趣的:(dp,动态规划,记忆化搜索,递推)