动态规划专题题解

方块与收纳盒

题目大意

就是给你一个长为n ,宽为1的收纳盒,然后,给你很多很多的长为1,长为2,宽都为1的方块,让你去放满这个收纳盒,看有多少种放的方案。

解题思路

这道题和上楼梯那个很像,当前能放方块的方案数一定是从上一个和上两个放方块的方案数转移过来的,所以可以直接递推,递归应该也可以吧,就是不知道会不会超时,我没试过。

代码如下

#include<stdio.h>
int main()
{
    int t;
    scanf("%d", &t);
    while(t--)
    {
        int n;
        scanf("%d", &n);
        long long a[n];
        a[0]=1, a[1]=2;
        for(int i=2;i<n;i++)
        {
            a[i]=a[i-1]+a[i-2];
        }
        printf("%lld\n", a[n-1]);
    }
}
​

可爱の星空

题目大意

题目就是给你n个点,让你把这n个点连在一起,你可以进行无数次连接,但每次连接都是有代价的,每次连接的代价大小是,将会被连接起来的两部分各部分所包含的点的数量的差值。问将所有点都连在一起,最小的代价是多少。

解题思路

这道题我们可以这样考虑,比如现在有n个点,连接成n个点之前,他是由两部分组成的,为了使这一次的代价尽可能小,那么我们肯定是将这n个点分为差值尽可能小的两部分,即如果当前的n为偶数,则他是由两个n/2的点连接起来的,如果是奇数,则他是由n/2和n/2+1的点连接起来的。对于这个过程我们让每一次的点都这样得来,那么我们所花费的代价将会最小。所以我们可以进行递归

代码如下

#include<stdio.h>
#include<string.h>
typedef long long LL;
LL n;
LL f(LL fn)
{
    if(fn==1||fn==2)
        return 0;
    LL sum=0;
    if(fn%2==0)
        return 2*f(fn/2);
    else
        return f(fn/2)+f((fn+1)/2)+1;
}
int main()
{
    int t;
    scanf("%d", &t);
    if(t)
    while(t--)
    {
        scanf("%lld", &n);
        printf("%lld\n", f(n));
    }
}

花店橱窗

题目大意

就是给你f朵花,然后有v个花瓶,而每朵花对于对于装在每个花瓶都有一个漂亮程度,然后,按顺序给你每朵花在每个花瓶中的漂亮程度,而你要按顺序将这些花放入花瓶,而且先放的花必须在后放的花的左边且每个花瓶只能放一朵花,问你如何放可以使得放完所有花后的总的漂亮程度最大,问你最大是多少,并且输出每朵花放所放花瓶的位置。

解题思路

对于这道题我们可以这样考虑,假设我们当前是在第x位置放花,那么我们在第x位置放当前花的最大漂亮程度一定是基于之前所有花的最大漂亮程度+当前位置放当前花的漂亮程度。而当我们把当前花对于所有能放的位置都这样放一遍之后,我们遍历这些值就可以得到当前及之前的所有花放好的最大漂亮程度。而这个思路对于上一朵花来说也是这样的,所以我们可以基于这个思路去进行动态规划

代码如下

#include<stdio.h>
#include<algorithm>
using namespace std;
typedef long long LL;
long long a[105][105];
int b[105][105];
int main()
{
	int f, v;
	scanf("%d%d", &f, &v);
	for(int i=1;i<=f;i++)
	{
		for(int j=1;j<=v;j++)
		{
			scanf("%lld", &a[i][j]);
		}
	}
	for(int i=1;i<=f;i++)
	{
		long long maxx=a[i-1][i-1];
		int maxxi=i-1;
		for(int j=i;j<=v;j++)
		{
            if(a[i-1][j-1]>maxx)
            {
				maxx=a[i-1][j-1];
				maxxi=j-1;
            }
            a[i][j]=maxx+a[i][j];
            b[i][j]=maxxi;
		}
	}
	long long sum=-1e18;
	int sumi;
	for(int i=f;i<=v;i++)
	{
		if(a[f][i]>sum)
		{
			sum=a[f][i];
			sumi=i;
		}
	}
	printf("%lld\n", sum);
	int c[f+1];
	c[f]=sumi;
	for(int i=f;i>=1;i--)
	{
		if(i!=f)
			c[i]=b[i+1][c[i+1]];
	}
	for(int i=1;i<=f;i++)
		printf("%d ", c[i]);
	return 0;
}

[NOIP2002]过河卒

题目大意

这道题的题意就是给你一个兵,他在棋盘的最左上方,棋盘大小是n*m的,而棋盘中有很多个马,马和马能一步走到的位置都是兵不能走的,而兵只能往下和往右走,问你兵从左上方(0, 0)点走到右下方(n, m)点总共有多少总方案

解题思路

这道题可以用搜索去做,不过有动态规划也很简单,我们可以这样考虑,走到当前位置的方案数一定是由走到当前位置左边一步和上边一步的方案数的总和。基于这个思路我们就可以进行递推了。我们只需要先将兵不能走的地方标记为-1,那么其余每个地方的方案数都是其左边和上边一步的方案数之和,我们只需要考虑左边一步和上边一步是不是-1

代码如下

#include<stdio.h>
using namespace std;
long long a[22][22];
int main()
{
    int n, m, x, y;
    scanf("%d%d%d%d", &n, &m, &x, &y);
    x=x+1;y=y+1;
    n=n+1;m=m+1;
    a[1][1]=1;
    a[x][y]=-1;
    if(x-1>=0&&y-2>=0)
        a[x-1][y-2]=-1;
    if(x+1<n&&y-2>=0)
        a[x+1][y-2]=-1;
    if(x-1>=0&&y+2<n)
        a[x-1][y+2]=-1;
    if(x+1<n&&y+2<n)
        a[x+1][y+2]=-1;
    
    if(x-2>=0&&y-1>=0)
        a[x-2][y-1]=-1;
    if(x+2<n&&y-1>=0)
        a[x+2][y-1]=-1;
    if(x-2>=0&&y+1<n)
        a[x-2][y+1]=-1;
    if(x+2<n&&y+1<n)
        a[x+2][y+1]=-1;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            if(i==1&&j==1)
                continue;
            if(a[i][j]==-1)
                continue;
            if(a[i-1][j]!=-1)
                a[i][j]+=a[i-1][j];
            if(a[i][j-1]!=-1)
                a[i][j]+=a[i][j-1];
        }
    }
    printf("%lld", a[n][m]);
}

[NOIP2008]传球游戏

题目大意

这道题就是有n个同学围城一圈,而总共有m次传球机会,每个人每次传球只能传个他左边或者右边的人,假设首先传球的叫小明,问你传完m次球后,有多少种方案能使球可以回到小明脚下。

解题思路

这道题我们可以这样去考虑,第x次传球,传到当前这个人脚下的方案数有y种,这y种是怎么来的呢?y是由x-1次时,传到他左边和右边的人的方案数之和,就得到了第x次,传到当前这个人脚下的方案数。基于这个思路我们就可以从第0次传球开始递推。当然,我们要考虑传到第0个和第n-1(即最后一个)人的时候,其上一次传球其实是由第1、n-1两人和第n-2、0两人传来的

代码如下

#include<stdio.h>
typedef long long LL;
int n, m;
LL a[33][33];
int main()
{
    scanf("%d%d", &n, &m);
    a[0][0]=1;
    for(int i=1;i<=m;i++)
    {
        for(int j=0;j<n;j++)
        {
            a[i][j]+=a[i-1][(j+1)%n];
            if(j==0)
            {
                if((j+1)%n!=(j-1+n))
                    a[i][j]+=a[i-1][(j-1+n)];
            }
            else
            {
                if((j+1)%n!=(j-1))
                    a[i][j]+=a[i-1][j-1];
            }
        }
    }
    /*
    for(int i=0;i<=m;i++)
    {
        for(int j=0;j<n;j++)
        {
            printf("%d ", a[i][j]);
        }
        printf("\n");
    }
    */
    printf("%lld", a[m][0]);
    return 0;
}

[NOIP2005]采药

题目大意

你有t秒的时间在山洞中采药,山洞中总共有m珠药,告诉你采每珠药需要的时间以及每珠药的价值,问你规定时间内最多能采多大价值的药

解题思路

我们的基本思路就是,对于每一珠药,我们都可以选择采还是不采,比如我们要采1, 2, 3,这3珠药,那么我们我们就有 都不采,采1, 采2, 采12, 采3, 采23, 采123这6钟采发,下面给出的第一种代码就是基于这个思路每次采当前珠的时候,考虑基于采上一珠的所有情况进行选择采还是不采,然后把选择后的价值存在二维数组中,这个二维数组的i是当前在采的珠,j是当前在采的珠的某种选择所需的时间,对于某种选择时间大于t的,我们直接抛弃。

代码如下

01背包的自己写法

#include<stdio.h>
#include<algorithm>
using namespace std;
int a[110][1010];
int main()
{
    int t, m;
    scanf("%d%d", &t, &m);
    int at[m+1], v[m+1];
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d", &at[i], &v[i]);
    }
    a[0][0]=-1;
    for(int i=1;i<=m;i++)
    {
        for(int j=0;j<=t;j++)
        {
            if(a[i-1][j]!=0)
            {
                if(a[i-1][j]==-1)
                {
                    a[i][j]=a[i-1][j];
                    if(j+at[i]<=t)
                        a[i][j+at[i]]=v[i];
                }
                else
                {
                    a[i][j]=max(a[i][j], a[i-1][j]);
                    if(j+at[i]<=t)
                        a[i][j+at[i]]=a[i-1][j]+v[i];
                }
            }
        }
    }
    /*
    for(int i=0;i<=m;i++)
    {
        for(int j=0;j<=t;j++)
        {
            printf("%d ", a[i][j]);
        }
        printf("\n");
    }
    */
    int maxx=0;
    for(int i=0;i<=t;i++)
    {
        maxx=max(maxx, a[m][i]);
    }
    printf("%d\n", maxx);
    return 0;
}

01背包模版写法

#include<stdio.h>
#include<algorithm>
using namespace std;
int main(void)
{
	int m,t;
	int a[105],b[105];
	int c[105][1005];
	scanf("%d%d", &t, &m);
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d", &a[i], &b[i]);
	}
	for(int i=1;i<=m;i++)
	{
		for(int j=1;j<=t;j++)
		{
			c[i][j]=c[i-1][j];
			if(j>=a[i])
			{
				c[i][j]=max(c[i-1][j],c[i-1][j-a[i]]+b[i]);
			}
		}
	}
	printf("%d", c[m][t]);
	return 0;
}

其他题目

下面的题和上面的题的解题思路类似,我就直接附上代码了

[NOIP2001]装箱问题

#include<stdio.h>
#include<algorithm>
using namespace std;
int b[31][20010];
int main()
{
    int v, n;
    scanf("%d%d", &v, &n);
    int a[n+1];
    for(int i=1;i<=n;i++)
        scanf("%d", &a[i]);
    b[0][0]=1;
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<=v;j++)
        {
            b[i][j]=b[i-1][j];
            if(j-a[i]>=0)
                b[i][j]=max(b[i][j], b[i-1][j-a[i]]);
        }
    }
    /*
    for(int i=0;i<=n;i++)
    {
        for(int j=0;j<=v;j++)
        {
            printf("%d ", b[i][j]);
        }
        printf("\n");
    }
    */
    for(int j=v;j>=0;j--)
    {
        if(b[n][j]==1)
        {
            printf("%d\n", v-j);
            return 0;
        }
    }
}

[NOIP2006]开心的金明

#include<stdio.h>
#include<algorithm>
using namespace std;
int a[30][30010];
int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    int v[m+1], vw[m+1];
    for(int i=1;i<=m;i++)
    {
        int p;
        scanf("%d%d", &v[i], &p);
        vw[i]=v[i]*p;
    }
    a[0][0]=-1;
    for(int i=1;i<=m;i++)
    {
        for(int j=0;j<=n;j++)
        {
            /*
            a[i][j]=a[i-1][j];
            if(j-v[i]>=0)
                a[i][j]=max(a[i][j], a[i-1][j-v[i]]+vw[i]);
            */
            if(a[i-1][j]!=0)
            {
                if(a[i-1][j]==-1)
                {
                    a[i][j]=a[i-1][j];
                    if(j+v[i]<=n)
                        a[i][j+v[i]]=vw[i];
                }
                else
                {
                    a[i][j]=max(a[i][j], a[i-1][j]);
                    if(j+v[i]<=n)
                        a[i][j+v[i]]=a[i-1][j]+vw[i];
                }
            }
        }
    }
    /*
    for(int i=0;i<=m;i++)
    {
        for(int j=0;j<=n;j++)
        {
            printf("%d ", a[i][j]);
        }
        printf("\n");
    }
    */
    //printf("%d\n", a[m][n]);
    int maxx=0;
    for(int i=0;i<=n;i++)
        maxx=max(maxx, a[m][i]);
    printf("%d\n", maxx);
    return 0;
}

「木」迷雾森林

#include<stdio.h>

const int mol=2333;
int a[3010][3010];

template<class T>inline void read(T &res)
{
char c;T flag=1;
while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;res=c-'0';
while((c=getchar())>='0'&&c<='9')res=res*10+c-'0';res*=flag;
}

int main()
{
    int m, n;
    read(m), read(n);
    for(int i=1;i<=m;i++)
    {
        for(int j=1;j<=n;j++)
        {
            read(a[i][j]);
        }
    }
    a[m][1]=-1;
    for(int i=m;i>=1;i--)
    {
        for(int j=1;j<=n;j++)
        {
            if(i==m&&j==1)
                continue;
            if(a[i][j]==1)
                continue;
            if(a[i+1][j]!=1)
                a[i][j]=(a[i+1][j]+a[i][j])%mol;
            if(a[i][j-1]!=1)
                a[i][j]=(a[i][j-1]+a[i][j])%mol;
        }
    }
    printf("%d\n", -((a[1][m])%mol));
    return 0;
}

[NOIP2004]合唱队形

#include<stdio.h>
#include<algorithm>
using namespace std;
int a[110], b[110], c[110];
int main()
{
    int n;
    scanf("%d", &n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d", &a[i]);
    }
    for(int i=1;i<=n;i++)
    {
        int maxxn=0;
        for(int j=0;j<i;j++)
        {
            if(a[i]>a[j])
                maxxn=max(maxxn, b[j]);
        }
        b[i]=maxxn+1;
    }
    for(int i=n;i>=1;i--)
    {
        int maxxn=0;
        for(int j=n+1;j>i;j--)
        {
            if(a[i]>a[j])
                maxxn=max(maxxn, c[j]);
        }
        c[i]=maxxn+1;
    }
    /*
    for(int i=0;i<=n+1;i++)
        printf("%d ", b[i]);
    printf("\n");
    for(int i=0;i<=n+1;i++)
        printf("%d ", c[i]);
    printf("\n");
    */
    int maxx=0;
    for(int i=1;i<=n;i++)
        maxx=max(maxx, b[i]+c[i]);
    printf("%d", n-(maxx-1));
    return 0;
}

  • 14
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值