初学算法----动态规划题目

 

向四周判断的动态规划:

滑雪

描述:

Michael喜欢滑雪百这并不奇怪, 因为滑雪的确很刺激。可是为了获得速度,滑的区域必须向下倾斜,而且当你滑到坡底,你不得不再次走上坡或者等待升降机来载你。Michael想知道载一个区域中最长的滑坡。区域由一个二维数组给出。数组的每个数字代表点的高度。下面是一个例子
 1  2  3  4 5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9

一个人可以从某个点滑向上下左右相邻四个点之一,当且仅当高度减小。在上面的例子中,一条可滑行的滑坡为24-17-16-1。当然25-24-23-...-3-2-1更长。事实上,这是最长的一条。

输入:

输入的第一行表示区域的行数R和列数C(1 <= R,C <= 100)。下面是R行,每行有C个整数,代表高度h,0<=h<=10000。

输出:

输出最长区域的长度。

样例输入:

5 5
1 2 3 4 5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9

样例输出:

25
这道题目写出记忆化搜索(递归)形式不难,但递推形式要一点技巧;
#include <bits/stdc++.h>
using namespace std;
int row, line, counter = 1, dp[100000], dx[4] = {0, 1, 0, -1}, dy[4] = {-1, 0, 1, 0};//这里dx,dy是写方向的常见写法;
struct E
{
    int x;
    int y;
    int h;
} maper[100000];

bool rule1(E a, E b)
{
    return a.h < b.h;
}

bool ifaround(int i, int j)
{
    for (int k = 0; k < 4; k++)
    {
        int rex = maper[i].x + dx[k];
        int rey = maper[i].y + dy[k];
        if (rex == maper[j].x && rey == maper[j].y)
        {
            return true;
        }
    }
    return false;
}

int main()
{
    //输入数据;
    cin >> row >> line;
    //将二维数组转化为一维数组,便于递推;
    for (int i = 1; i <= row; i++)
    {
        for (int j = 1; j <= line; j++)
        {
            dp[counter] = 1;//这里再强调一下memset只能初始化0,-1而不是1;
            maper[counter].x = j;
            maper[counter].y = i;
            cin >> maper[counter].h;
            counter++;
        }
    }
    //将数组按高度从小到大排列;
    sort(maper + 1, maper + counter, rule1);
    //递推dp数组;
    for (int i = 1; i < counter; i++)
    {
        for (int j = i - 1; j >= 1; j--)
        {
            if (ifaround(i, j) && maper[i].h > maper[j].h )//这里加上maper[i].h > maper[j].h 是为了防止高度重复的情况;
            {
                //因为有4种走法,要选出最大长度的走法;
                dp[i] = max(dp[j] + 1, dp[i]);
            }
        }
    }
    int answer = 0;
    for (int i = 1; i < counter; i++)
    {
        answer = max(answer, dp[i]);
    }
    cout << answer;
    return 0;
}
多亏了这位大佬的测试数据让我写出来了:https://blog.csdn.net/huanghanqian/article/details/51646241;
 
 
与广度优先搜索十分相像的题目(不是在再求最值,而是问个数):

神奇的口袋

描述:

有一个神奇的口袋,总的容积是40,用这个口袋可以变出一些物品,这些物品的总体积必须是40。John现在有n个想要得到的物品,每个物品的体积分别是a1,a2……an。John可以从这些物品中选择一些,如果选出的物体的总体积是40,那么利用这个神奇的口袋,John就可以得到这些物品。现在的问题是,John有多少种不同的选择物品的方式。

输入:

输入的第一行是正整数n (1 <= n <= 20),表示不同的物品的数目。接下来的n行,每行有一个1到40之间的正整数,分别给出a1,a2……an的值。

输出:

输出不同的选择物品的方式的数目。

样例输入:

3
20
20
20

样例输出3

解法1:广度优先搜索: 我这个dfs写的有点奇葩,一般dfs的函数初始调用是从底部开始而我这里是dfs(n,40)

#include<bits/stdc++.h>
using namespace std;
int itemsv[25],allway=0,n;
//dfs的作用是从前面的k件物品中,挑选出体积是nowallv的各个物品;
void dfs (int k,int nowallv)
{
    if (k<0 || nowallv<0)//这里k并不能判断为==0,若k是<=0mowallv
    //,那么就会少掉当第一个正好挑出使mowallv==0,而没有让allway++的情况;
    {
        return ;
    }
    if (nowallv==0)
    {
        allway++;
        return ;
    }
    for (int i=k;i>=1;i--)
    {
        nowallv-=itemsv[i];
        dfs(i-1,nowallv);
        nowallv+=itemsv[i];//回溯,这个回溯效果会帮助实现在第i个物品时不去拿这个物品;
同时我也不用列个什么for 循环来,要起始点从i=n开始到i=1;回溯可以实现其全部情况,不管是起始从n开始,还是25等开始;
    }
}
int main()
{
    cin>>n;
    for (int i=1;i<=n;i++)
    {
        cin>>itemsv[i];
    }
    dfs (n,40);
    cout<<allway;
    return 0;
}

解法2 : 普通递归
这个是为写递推做准备,会发现递推大多是从最后的情况写起;

 

#include<bits/stdc++.h>
using namespace std;
int itemsv[45];
//递归的作用是从前面的k件物品中,挑选出体积是nowallv的各个物品;
int recursion (int k,int nowallv)
{
    if (nowallv==0)
    {
        return 1;
    }
    if (k<0 || nowallv<0)
    {
        return 0;
    }
    return recursion(k-1,nowallv)+recursion(k-1,nowallv-itemsv[k]);//这个有点像放苹果的那道 return ;感觉很有灵魂
}
int main()
{
    int n;
    cin>>n;
    for (int i=1;i<=n;i++)
    {
        cin>>itemsv[i];
    }
    int allway=recursion (n,40);
    cout<<allway;
    return 0;
}

 

 
解法3: 动态规划:
#include<bits/stdc++.h>
using namespace std;
int itemsv[25],dp[25][45];
//dp[i][j]数组的作用是记录在前面i件物品中挑选总体积小于j的总方法;
int main()
{
    int n;
    cin>>n;
    for (int i=1;i<=n;i++)
    {
        cin>>itemsv[i];
    }
    //初始化边界条件;
    memset(dp,0,sizeof(dp));
    for (int i=0;i<=n;i++)
    {
        dp[i][0]=1;//结束条件,让最后剩余体积等于0的都为一种方法;
    }
    for (int i=1;i<=n;i++)
    {
        for (int j=1;j<=40;j++)
        {
            int restv=j-itemsv[i];
            if (restv<0)
            {
                dp[i][j]=dp[i-1][j]+0;
            }
            else
            {
                dp[i][j]=dp[i-1][j]+dp[i-1][restv];
            }
        }
    }
    cout<<dp[n][40];
    return 0;
}

 可以提高效率的动态规划:

复杂的整数划分问题

描述:

将正整数n 表示成一系列正整数之和,n=n1+n2+…+nk, 其中n1>=n2>=…>=nk>=1 ,k>=1 。
正整数n 的这种表示称为正整数n 的划分。

输入:

标准的输入包含若干组测试数据。每组测试数据是一行输入数据,包括两个整数N 和 K。
(0 < N <= 50, 0 < K <= N)

输出:

对于每组测试数据,输出以下三行数据:
第一行: N划分成K个正整数之和的划分数目
第二行: N划分成若干个不同正整数之和的划分数目
第三行: N划分成若干个奇正整数之和的划分数目

样例输入:

5 2

样例输出:

2
3
3

注释:

第一行: 4+1, 3+2,
第二行: 5,4+1,3+2
第三行: 5,1+1+3, 1+1+1+1+1+1
这道题可以说完全是一个递归题,但可以用动态规划提高效率;(这道题与放苹果简直异曲同工,我递归菜的难,还是不会以下有大佬的启发)
我的错误代码,下次来改吧:
#include<bits/stdc++.h>
#include<cstring>
using namespace std;
int dp1[60][60],dp2[60][60],dp3[60][60];
//dp1[n][k]的作用是将数字n划分成k个正整数之和的划分数目;

//dp2[n][k]的作用是将数字n划分成若干个不同正整数之和的划分数目,
//其中每个数字大小不超过k

//dp3[n][k]的作用是将数字n划分成若干个奇正整数之和的划分数目,
//其中每个数字大小不超过k
int main()
{
    int n,k;
    while (cin>>n>>k)
    {
        //第一行输出:
        memset (dp1,0,sizeof (dp1));
        for (int i=1;i<=n;i++)
        {
            dp1[i][i]=1;
            dp1[i][1]=1;
        }
        for (int i=k+1;i<=n;i++)
        {
            for (int j=2;j<=k;j++)
            {
                dp1[i][j]=dp1[i-1][j-1]+dp1[i-j][j];
            }
        }
        cout<<dp1[n][k]<<endl;
        //第二行输出:
        memset(dp2,0,sizeof (dp2));
        for (int i=0;i<=n;i++)
        //初始化,当k==0时,每个数字大小不超过k,
        //则说明是不可能的,全为0;当n==0时,说明其将整个数都划分出去了
        //又有dp[i][j]当i<j时dp[i][j]==dp[i][i],则干脆全初始化为1;
        {
            dp2[0][i]=1;
            //dp2[i][1]=1;这条语句是不行的,因为划分成若干个不同正整数之和;
        }
        for (int i=1;i<=n;i++)
        {
            for (int j=1;j<=n;j++)
            {
                if (i<j)
                {
                    dp2[i][j]=dp2[i][i];
                }
                else
                {
                    dp2[i][j]=dp2[i-j][j-1]+dp2[i][j-1];
                }
            }
        }
        cout<<dp2[n][n]<<endl;
        //第三行输出:
        memset(dp3,0,sizeof (dp3));
        for (int i=0;i<=n;i++)
        {
            dp3[0][i]=1;
            dp3[i][1]=1;
        }
        for (int i=1;i<=n;i++)
        {
            for (int j=2;j<=n;j++)
            {
                if (i<j)
                {
                    dp3[i][j]=dp3[i][i];
                }
                else
                {
                    if (j%2==0)
                    {
                        dp3[i][j]=dp3[i][j-1];
                    }
                    else
                    {
                        dp3[i][j]=dp3[i-j][j]+dp3[i][j-1];
                    }
                }
            }
        }
        cout<<dp3[n][n]<<endl;
    }
    return 0;
}
 
posted @ 2022-01-15 18:50  次林梦叶  阅读(42)  评论(0)    收藏  举报