基础动态规划(DP)总结

基础动态规划(DP)总结

这个真的是旷日持久啊,之前很长一段时间都是看见DP就害怕,基本靠背板子过题。。。直到前几天的字符串距离问题才如梦方醒,真正理解。那道题的链接:https://blog.csdn.net/soul_mingling/article/details/104571309
希望不理解DP的看了之后也能理解过来。

文章目录

  • 基础动态规划(DP)总结
    • 【动态规划】探索数字迷塔
    • 【动态规划】圣诞树
    • 传球游戏
    • 【动态规划】黑熊过河
    • 【动态规划】抢金块

【动态规划】探索数字迷塔

题目描述:
晶晶最近迷上了数字迷宫游戏,整天沉浸在一串串看似简单的数字中自得其乐。数字迷宫游戏的魅力体现在变化中隐含着不变的规律,归纳是探究数字迷宫的法宝之一。下图就是一个由线连接起来的数字小方格组成的数字迷塔。
这个迷塔共n层,它由n×(n+1)/2个小方格组成。每个小方格中都有一个数字,并且连着下一层的两个小方格。现从塔顶走到塔底,每一步只能走到相邻的方格中,则经过方格的数字之和最大值是多少?这个问题晶晶已经琢磨一天了,她感觉异常棘手。你能帮帮她吗?
输入:
输入数据共n+1行,第1行是一个整数n(1≤n≤1000),表示数字迷塔的高度,接下来用n行数字表示数字迷塔,其中第i行有i个正整数,且所有的正整数均不大于100。
输出 :
输出可能得到的最大和。
样例输入:
5
9
12 15
10 6 8
2 18 9 5
19 7 10 4 16
样例输出:
59
提示:
样例说明:9→12→10→18→10
从下往上扫,dp数组的第一维和第二维分别代表行和列,最后输出即可。

#include 
using namespace std;
int ans[1005][1005];
int dp[1005][1005];
int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=i;j++)
        {
            cin>>dp[i][j];
        }
    }
    for(int i=1;i<=n;i++)
        ans[n][i]=dp[n][i];
    for(int i=n;i>1;i--)
    {
        for(int j=1;j<i;j++)
        {
            ans[i-1][j]=max(ans[i][j],ans[i][j+1])+dp[i-1][j];
        }
    }
    cout<<ans[1][1]<<endl;
    return 0;
}

【动态规划】圣诞树

题目描述:
圣诞特别礼物挂在一棵圣诞树上,这棵树有n层,每层有一件礼物,每件礼物都有一个价值,有的礼物还有一些连接线,与下层的礼物相连。领取礼物的规则如下:任选一件礼物,它的下面如果有连接线,则可以继续取它连接的礼物,依此类推直至取到没有连接线的礼物才结束。你如果是第一个去取,怎样取才能获得最大的价值呢?请你编一程序解决这一问题。
输入:
第1行只有一个数据n(n≤100),表示有n层礼物,以下有n行数据,分别表示第1~n层礼物的状态,每行至少由一个数据构成,且第一个数据表示该礼物的价值,后面的数据表示它与哪些层的礼物相连,如果每行只有一个数据则说明这层礼物没有与下层礼物相连,每个数据大小均不超过10000。
输出:
只有一个数,表示获得的最大价值。
样例输入 :
3
12 2 3
20
30
样例输出:
42
其实分析起来和上个题差不多,也是从下层往上层扫,状态只有“选”和“不选”两种,但是这个题读入比较烦,也不能用cin,因为cin读数字的时候会把空格一并读进去。

#pragma GCC optimize(3,"Ofast","inline")
#pragma G++ optimize(3)
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<ll,ll> pll;
typedef pair<int,int> pii;
typedef queue<int> q_i;
typedef queue<string> q_s;
typedef queue<double> q_d;
typedef queue<ll> q_ll;
typedef priority_queue<int> pq_i;
typedef priority_queue<string> pq_s;
typedef priority_queue<double> pq_d;
typedef priority_queue<ll> pq_ll;
typedef stack<int> s_i;
typedef stack<string> s_s;
typedef stack<double> s_d;
typedef stack<ll> s_ll;
#define rep(i,l,r) for(int i=l;i<=r;i++)
#define per(i,l,r) for(int i=r;i>=l;i--)
#define eif else if
#define mm(dp) memset(dp,0,sizeof(dp))
const double e=2.71828182845;
const double pi = acos(-1.0);
void read(int &x)
{
    char ch=getchar(); x=0;
    for(;ch<'0'||ch>'9';ch=getchar());
    for(;ch>='0'&&ch<='9';x=x*10+ch-'0',ch=getchar());
}
inline void write(int x)
{
     if(x<0) putchar('-'),x=-x;
     if(x>9) write(x/10);
     putchar(x%10+'0');
}
double InvSqrt (double x)
{
        double xhalf = 0.5f*x;
        int i = *(int*)&x;
        i = 0x5f3759df - (i >> 1);
        x = *(double*)&i;
        x = x*(1.5f - xhalf*x*x);
        return 1/x;
}
int n;
int a[101],dp[101];
int main()
{
    //std::ios::sync_with_stdio(false);这道题不要写这个,要不然输入会有问题
    //cout.tie(0);
    read(n);
    char t;
    rep(i,1,n)
    {
        cin>>a[i];
        dp[i]=a[i];
        scanf("%c",&t);
        while(t==' ')
        {
            int num;
            scanf("%d",&num);
            flag[i][num]++;
            scanf("%c",&t);
        }
    }
    per(i,1,n)
    {
        int maxn=dp[i];
        rep(j,i+1,n)
        {
            if(flag[i][j])
            {
                maxn=max(maxn,dp[i]+dp[j]);
            }
        }
        dp[i]=maxn;
    }
    int maxn=0;
    rep(i,1,n)
    {
        maxn=max(maxn,dp[i]);
    }
    write(maxn);
    return 0;
}

传球游戏

题目描述:
上体育课时,墨老师经常带着同学们一起做游戏。这次,墨老师带着同学们一起做传球游戏,游戏规则是这样的:N个同学站成一个圆圈,其中的一个同学手里拿着一个球,当老师吹哨子时开始传球,每个同学可以把球传给自己左右的两个同学中的一个(左右任意),当老师再次吹哨子时,传球停止,此时拿着球没传出去的那个同学就是败者,要给大家表演一个节目。
聪明的张琪曼提出一个有趣的问题:有多少种不同的传球方法可以使得从张琪曼手里开始传的球,传了M次以后,又回到张琪曼手里。两种传球的方法被称作不同的方法,当且仅当这两种方法中,接到球的同学按接球顺序组成的序列是不同的。比如有3个同学1号、2号、3号,并假设张琪曼为1号,球传了3次回到张琪曼手里的方式有1à2à3à1和1à3à2à1,共两种。
输入:
有两个用空格隔开的整数N,M(3≤N≤30,1≤M≤30)。
输出:
只有一个整数,表示符合题目的方法数。
样例输入:
3 3
样例输出:
2
设一个二维数组dp[31][31],dp[i][j]代表传了i次,球在j同学手中的方案数,由他两边的同学方案数相加即可,需要注意的是,这是一个圈,也就是说1同学和n同学是左右相邻的。

#pragma GCC optimize(3,"Ofast","inline")
#pragma G++ optimize(3)
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<ll,ll> pll;
typedef pair<int,int> pii;
typedef queue<int> q_i;
typedef queue<string> q_s;
typedef queue<double> q_d;
typedef queue<ll> q_ll;
typedef priority_queue<int> pq_i;
typedef priority_queue<string> pq_s;
typedef priority_queue<double> pq_d;
typedef priority_queue<ll> pq_ll;
typedef stack<int> s_i;
typedef stack<string> s_s;
typedef stack<double> s_d;
typedef stack<ll> s_ll;
#define rep(i,l,r) for(int i=l;i<=r;i++)
#define per(i,l,r) for(int i=r;i>=l;i--)
#define eif else if
#define mm(dp) memset(dp,0,sizeof(dp))
const double e=2.71828182845;
const double pi = acos(-1.0);
void read(int &x)
{
    char ch=getchar(); x=0;
    for(;ch<'0'||ch>'9';ch=getchar());
    for(;ch>='0'&&ch<='9';x=x*10+ch-'0',ch=getchar());
}
inline void write(int x)
{
     if(x<0) putchar('-'),x=-x;
     if(x>9) write(x/10);
     putchar(x%10+'0');
}
double InvSqrt (double x)
{
        double xhalf = 0.5f*x;
        int i = *(int*)&x;
        i = 0x5f3759df - (i >> 1);
        x = *(double*)&i;
        x = x*(1.5f - xhalf*x*x);
        return 1/x;
}
int n,m;
int dp[31][31];
int main()
{
    std::ios::sync_with_stdio(false);
    cout.tie(0);
    read(n);
    read(m);
    dp[0][1]=1;
    rep(i,1,m)
    {
        rep(j,1,n)
        {
            if(j==1)
                dp[i][j]=dp[i-1][2]+dp[i-1][n];
            else if(j==n)
                dp[i][j]=dp[i-1][1]+dp[i-1][n-1];
            else
                dp[i][j]=dp[i-1][j+1]+dp[i-1][j-1];
        }
    }
    write(dp[m][1]);
    return 0;
}

【动态规划】黑熊过河

题目描述:
晶晶的爸爸给晶晶出了一道难题:有一只黑熊想过河,但河很宽,黑熊不会游泳,只能借助河面上的石墩跳过去,它可以一次跳一墩,也可以一次跳两墩,但是每跳一次都会耗费一定的能量,黑熊最终可能因能量不够而掉入水中。所幸的是,有些石墩上放了一些食物,这些食物可以给黑熊增加一定的能量。问黑熊能否利用这些石墩安全地抵达对岸?请计算出抵达对岸后剩余能量的最大值。
输入:
第1行包含两个整数P(黑熊的初始能量),Q(黑熊每次起跳时耗费的能量),0≤P,Q≤1000;
第2行只有一个整数n(1≤n≤106),即河中石墩的数目;
第3行有n个整数,即每个石墩上食物的能量值ai(0≤ai≤1000)。
输出:
仅1行,若黑熊能抵达对岸,输出抵达对岸后剩余能量的最大值;若不能,则输出“NO”。
样例输入:
12 5
5
0 5 2 0 7
样例输出:
6
两个状态,跳两步和跳一步,选择最优的跳。这个题有个坑,一开始我是按从后往前规划的,但是发现可能熊会“复活”(能量不足以跳下一格,但加上下一格上的能量后“由负转正”),后来发现正着也可以,这样特判“熊复活”的情况就简单了很多。

#include
using namespace std;
int n,q,p,num,a[1000005],dp[1000005];
int main()
{
   cin>>p>>q>>n;
   for(int i=1;i<=n;i++)
      cin>>a[i];
   dp[0]=p;
   for(int i=1;i<=n+1;i++)
   {
      if(i==1) num=dp[0];
      else num=max(dp[i-1],dp[i-2]);
      if(num>=q) dp[i]=num-q+a[i];
      else
      {
           cout<<"NO"<<endl;     
           return 0;
      }
   }
   cout<<dp[n+1]<<endl;    
   return 0;
}

【动态规划】抢金块

题目描述:
地面上有一些格子,每个格子上面都有金块,但不同格子上的金块有不同的价值,你一次可以跳S至T步(2≤S 输入:
第1行是格子个数n (n<1000);
第2行是S和T,保证T大于s(2≤S 第3行是每个格子上的金块价值Pi (Pi<10000)。
输出:
输出最多可以获得的金块的总价值。
样例输入:
10
2 3
4 5 8 2 8 3 6 7 2 9
样例输出:
36
提示:
样例说明:跳1、3、5、8、10,总价值:4+8+8+7+9=36。
题解:
这个题看起来和上个题差不多,但倘若用上个题的做法,则会WA掉。为什么呢?上道题每次只能跳1格或者2格,说明每个格子都有可能被跳到;但这道题是s步起跳的,就导致某些格子根本无法被跳到。比如:s=4,t=5,则2,3,4,6,7,8……就都无法被跳到。这时如果我们再使用朴素线性dp的话,会导致最终结果不是从1号格子起跳的,而这是题目所不允许的。所以我们需要先对数组进行预处理,标记可以跳到的点。可以bfs也可以dfs,但是bfs会爆内存(如图)。
在这里插入图片描述
所以我换成了dfs来解决预处理问题,就不会出现爆内存的情况了。

#pragma GCC optimize(3,"Ofast","inline")
#pragma G++ optimize(3)
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<ll,ll> pll;
typedef pair<int,int> pii;
typedef queue<int> q_i;
typedef queue<string> q_s;
typedef queue<double> q_d;
typedef queue<ll> q_ll;
typedef priority_queue<int> pq_i;
typedef priority_queue<string> pq_s;
typedef priority_queue<double> pq_d;
typedef priority_queue<ll> pq_ll;
typedef stack<int> s_i;
typedef stack<string> s_s;
typedef stack<double> s_d;
typedef stack<ll> s_ll;
#define rep(i,l,r) for(int i=l;i<=r;i++)
#define per(i,l,r) for(int i=r;i>=l;i--)
#define eif else if
#define mm(dp) memset(dp,0,sizeof(dp))
const double e=2.71828182845;
const double pi = acos(-1.0);
void read(int &x)
{
    char ch=getchar(); x=0;
    for(;ch<'0'||ch>'9';ch=getchar());
    for(;ch>='0'&&ch<='9';x=x*10+ch-'0',ch=getchar());
}
inline void write(int x)
{
     if(x<0) putchar('-'),x=-x;
     if(x>9) write(x/10);
     putchar(x%10+'0');
}
double InvSqrt (double x)
{
        double xhalf = 0.5f*x;
        int i = *(int*)&x;
        i = 0x5f3759df - (i >> 1);
        x = *(double*)&i;
        x = x*(1.5f - xhalf*x*x);
        return 1/x;
}
int n,s,t;
int flag[10001];
void dfs(int num)
{
    flag[num]=1;
    rep(i,s,t)
    {
        if(flag[num+i]==0&&num+i<=n)
        {
            dfs(num+i);
        }
    }
}
int main()
{
    std::ios::sync_with_stdio(false);
    cout.tie(0);
    cin>>n;
    cin>>s>>t;
    int a[n+1];
    int dp[n+1];
    mm(flag);
    mm(dp);
    dfs(1);
    rep(i,1,n)
    {
        cin>>a[i];
    }
    dp[1]=a[1];
    rep(i,2,n)
    {
        int num=0;
        rep(j,s,t)
        {
            if(flag[i-j]==1)
                num=max(num,dp[i-j]);
            dp[i]=a[i]+num;
        }
    }
    cout<<dp[n]<<endl;
    return 0;
}

你可能感兴趣的:(训练营,其他DP,动态规划)