DP专题练习题解

Contents:

  • 奶牛的锻炼
    • Description
    • Input
    • Output
      • exerin
      • exerout
    • Hint
    • Solution
  • 2雷涛的小猫
    • Description
    • Input
    • Output
      • catin
      • catout
    • Hint
    • Solution
  • 3不等数列
    • Description
    • Input
    • Output
      • seqin
      • seqout
    • Hint
    • Solution
  • 4清理垃圾
    • Description
    • Input
    • Output
      • cleanin
      • cleanout
    • Hint
    • Solution

奶牛的锻炼

(exer.c/.cpp/.pas)

Description:

奶牛Bessie有N分钟时间跑步,每分钟她可以跑步或者休息。若她在第i分钟跑步,可以跑出D_i米,同时疲倦程度增加1(初始为0)。若她在第i分钟休息,则疲倦程度减少1。无论何时,疲倦程度都不能超过M。另外,一旦她开始休息,只有当疲惫程度减为0时才能重新开始跑步。在第N分钟后,她的疲倦程度必须为0。

Input:

输入文件名为(exer.in)。
第一行,两个整数,代表N和M。 接下来N行,每行一个整数,代表D_i。

Output

输出文件名为(exer.out)。
Bessie想知道,她最多能跑的距离。

exer.in

5 2
5 3 4 2 10

exer.out

9

Hint

N < = 2000 , M < = 500 , D_i < = 1000


Solution:

看到有两种状态,一开始想用f[i][1/0]表示第i分钟跑步或休息的最大价值,但是转移的时候因为不知道是从何时开始跑步或休息,所以很难列方程。所以可以用f[i][j][1/0]表示第i分钟跑步或休息,并能记录疲倦值的最值。
但是大可不必这么麻烦:
状态:f[i][j]表示前i分钟,疲倦值为j时的最远距离。
输出:f[n][0]
转移:f[i][j]=max(f[i][j],f[i-1][j-1]+a[i]) —>跑步
f[i][0]=f[i-1][0]
f[i][0]=max(f[i][0],f[i-j][j])—>休息的两种状态
TIPS:

  1. f清成-1
  2. f[0][0]=0;
  3. 休息的第二种状态j的范围是j∈[1,min(i,m)]

Code:

#include 
#include 
#define MAXN 2500
int n,m;
int a[MAXN],f[MAXN][600];
int min(int a,int b){return aint max(int a,int b){return a>b?a:b;}
int main()
{
    freopen("exer.in","r",stdin);
    freopen("exer.out","w",stdout);
    memset(f,-1,sizeof(f));
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
    }
    f[0][0]=0;
    for(int i=1;i<=n;i++)
    {
        f[i][0]=f[i-1][0];
        for(int j=1;j<=min(i,m);j++)
        {
            f[i][0]=max(f[i][0],f[i-j][j]);
        }
        for(int j=1;j<=m;j++)
        {
            f[i][j]=max(f[i][j],f[i-1][j-1]+a[i]);
        }
    }   
    printf("%d\n",f[n][0]); 
    return 0;

2、雷涛的小猫

(cat.c/.cpp/.pas)

Description

DP专题练习题解_第1张图片
DP专题练习题解_第2张图片

Input

输入文件名为(cat.in)。
DP专题练习题解_第3张图片

Output

输出文件名为(cat.out)。

cat.in

3 10 2
3 1 4 10
6 3 5 9 7 8 9
5 4 5 3 6 9

cat.out

8

Hint

DP专题练习题解_第4张图片


Solution:

状态:f[i]表示高度为i时吃到最多的柿子
输出:f[1]
转移:g[j]=max(g[j],tmp)+hash[j][i];
f[i]=max(f[i],g[j]);
TIPS:

  1. 用i枚举H时显然要倒序,但是当i+delta>=h时要特判
  2. 好好理解状态转移方程
    Code:
#include 
#include 
#define MAXN 2500
int n,h,d;
int f[MAXN],g[MAXN],a[MAXN],hash[MAXN][MAXN];
int max(int a,int b){return a>b?a:b;}
int main()
{
    freopen("cat.in","r",stdin);
    freopen("cat.out","w",stdout);
    scanf("%d%d%d",&n,&h,&d);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        for(int j=1;j<=a[i];j++)
        {
            int p;
            scanf("%d",&p);
            hash[i][p]++;
        }
    }
    for(int i=h;i>=1;i--)
    {
        int tmp=0;
        if(i+d<=h)
        {
            tmp=f[i+d];
        }
        for(int j=1;j<=n;j++)
        {
            g[j]=max(g[j],tmp)+hash[j][i];
            f[i]=max(f[i],g[j]);
        }
    }  
    printf("%d\n",f[1]);
    return 0;
}    

3、不等数列

(seq.c/.cpp/.pas)

Description:

将1到n任意排列,然后在排列的每两个数之间根据他们的大小关系插入“>”和“<”。问在所有排列中,有多少个排列恰好有k个“<”。答案对2012取模。

Input

输入文件名为(seq.in)。
第一行2个整数n,k。

Output

输出文件名为(seq.out)。
一个整数表示答案。

seq.in

5 2

seq.out

66

Hint

对于30%的数据:n <= 10
对于100%的数据:k < n <= 1000


Solution:

对于前30%的数据可以打表找规律
Code:

#include 
#include 
#include 
#define MAXN 1000
using namespace std; 
int n,k,ans;
int a[MAXN];
int count(int a[])
{
    int cnt=0;
    for(int i=2;i<=n;i++)
    {
        if(a[i]>a[i-1])
        {
            cnt++;
        }
    }
    return cnt;
}
int main()
{
    freopen("seq.in","r",stdin);
    freopen("seq.out","w",stdout);
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++)
    {
        a[i]=i;
    }
    do
    {
        if(count(a)==k)
        {
            ans++;
            ans%=2012;
        }
    }while(next_permutation(a+1,a+1+n));

    printf("%d\n",ans%2012);
    return 0;
}

具体分析就显然了,先一行一行来分析
f[n][1]=f[n-1][1]*2+(n-1):
现在我们就希望知道k=1的情况了,能否知道当k>=2的情况,但是事实是并非那么简单。
把所有情况列出来

  • 247=120*2+1*7
  • 4293=1191*3+120*6
  • 15619=2416*4+1191*5
    所以就可以找到规律了,f[i][j]=f[i-1][j]*(j+1)+(i-j)*f[i-1][j-1];

所以你看到了,我就是枚举找规律做的..
当然这道题正解是考虑不同的位置,不同的情况具体分析。式子是一样的.
Code:

#include 
#include 
#define MOD 2012
int n,k;
int f[1200][1200];
void pre_()
{
    f[0][0]=1;
    f[1][0]=1;
}
int main()
{
    freopen("seq.in","r",stdin);
    freopen("seq.out","w",stdout);
    pre_();
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++)
    {
        f[i][0]=1;
        for(int j=1;j<=k;j++)
        {
            f[i][j]=((f[i-1][j]*(j+1))%MOD+((i-j)*f[i-1][j-1])%MOD)%MOD;
            f[i][i-1]=1;
        }
    }
    printf("%d\n",f[n][k]%MOD);
    return 0;
}

4、清理垃圾

(clean.c/.cpp/.pas)

Description:

Candy家里总共有n个垃圾等待处理,每个垃圾对于Candy和飘飘乎居士处理的时间都是不同的,而且每个垃圾只需要一个人处理。当然,Candy和飘飘乎居士可以同时处理不同的垃圾。记两人中耗费最长时间为最后总时间。Candy希望能够尽快的处理完所有的垃圾,因此,他想要知道处理完这些垃圾最少需要耗费多少时间?

Input

输入文件名为(clean.in)。
第一行一个正整数n,表示一共有n个垃圾需要处理
接下来一个2*n的矩阵。
矩阵第一行第i个数表示candy处理第i个垃圾所需消耗的时间
矩阵第二行第i个数表示飘飘乎居士处理第i个垃圾所需消耗的时间

Output

输出文件名为(clean.out)。
一行,最后耗费的时间

clean.in

5
2 4 1 4 5
2 1 3 4 1

clean.out

5

Hint

Candy完成垃圾3与垃圾4的清理,耗时为5
飘飘乎居士完成垃圾1 2 5的清理,耗时为4,由于Candy耗费的时间较长,所以记Candy耗费时间为最后总时间,所以最后答案为5。
·对于30%的数据
0 < n<=30
对于100%的数据 0< n<=1000,Candy和飘飘乎居士处理每个垃圾的时间<=10,对任何一个人处理所有垃圾时间总和<=4000


Solution:

不会啊,怎么办?
暴力30分,贪心70分,加在一起就A了~并且分数无交集。
Code:

#include 
#include 
#include 
#define MAXN 1500
using namespace std;
int n,k;
int sum_a,sum_b,ans=0x3f3f3f3f;
int a[MAXN],b[MAXN],tmp[MAXN];
int cmp(int a,int b){return a>b?1:0;}
int max(int a,int b){return a>b?a:b;}
int min(int a,int b){return avoid dfs(int deep,int cnt1,int cnt2)
{
    if(cnt1>ans||cnt2>ans)
        return ;
    if(deep>n)
    {
        ans=min(ans,max(cnt1,cnt2));
        return ;
    }
    dfs(deep+1,cnt1+a[deep],cnt2);
    dfs(deep+1,cnt1,cnt2+b[deep]);
}
int main()
{
    freopen("clean.in","r",stdin);
    freopen("clean.out","w",stdout);
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
    }
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&b[i]);
    }
    if(n<=30)
    {
        dfs(1,0,0);
        printf("%d\n",ans);
        return 0;
    }
    for(int i=1;i<=n;i++)
    {
        if(a[i]>b[i])
        {
            sum_b+=b[i];
        }
        if(a[i]if(a[i]==b[i])
        {
            tmp[++k]=a[i];
        }
    }
    sort(tmp+1,tmp+1+k,cmp);
    for(int i=1;i<=k;i++)
    {
        if(sum_a<=sum_b)
        {
            sum_a+=tmp[i];
            continue;
        }
        if(sum_a>sum_b)
        {
            sum_b+=tmp[i];
            continue;
        }
    }
    printf("%d\n",max(sum_a,sum_b));
    return 0;
}

正解:
用f[i,j]表示:完成前i项任务,若Candy花了j分钟,
那么飘飘乎居士最少花f[i,j]分钟。
a[i]表示:Candy完成第i项任务所花的时间;b[i]表示:飘飘乎居士完成第i项任务所花的时间。
则方程为:f[i,j]=Min{f[i-1,j]+b[i],f[i-1,j-a[i]]} 。当j

#include 
#include 
#define MAXN 1000
int n,sum,ans=0x3f3f3f;
int a[MAXN],b[MAXN],f[MAXN][MAXN<<2]; 
int min(int a,int b){return aint max(int a,int b){return a>b?a:b;}
int main()
{
    freopen("clean.in","r",stdin);
    freopen("clean.out","w",stdout);
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        sum+=a[i];
    }
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&b[i]);
    }
    memset(f,0x3f,sizeof(f));
    f[0][0]=0;
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<=sum;j++)
        {
            if(j1][j]+b[i];
            }
            else f[i][j]=min(f[i-1][j]+b[i],f[i-1][j-a[i]]);
        }
    }
    for(int j=0;j<=sum;j++)
    {
        ans=min(ans,max(j,f[n][j]));
    }
    printf("%d\n",ans);
    return 0;
} 

你可能感兴趣的:(noip,推荐,题解,dp)