杭电60道DP问题总结(二)


接着上一篇文章。

上次一次写的有点长了,保存好半天不成功,这次短一点吧,5道题目。

题目链接地址:http://acm.hdu.edu.cn/problemclass.php?id=516

ProID 1081 To The Max

题目大意是,给一个二维数组,让你求一个和最大的子矩阵。

也算是一道比较简单的题目了,这个除了用暴力法外,比较经典的做法是DP,这个其实也是那个最大子序列和的一个变种。来看看为什么。

比如说求这个矩阵的最大子序列和:

0 -2 -7 0

9 2 -6 2

对于第一行来说,其实就是求一个连续子序列之和,对于两行来说,把他们加起来得到序列 9,0,-13,2,同样也是求最大子序列和。好了问题解决了。

代码如下:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int n;

int data[101][101];
int sumdata[101];
int main()
{
    while(scanf("%d",&n)!=EOF)
    {
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                scanf("%d",&data[i][j]);

        int maxNum=-100000;
        for(int i=1;i<=n;i++)//行递增
        {
            memset(sumdata,0,sizeof(sumdata));
            for(int j=i;j<=n;j++)//高度从i到n的矩阵
            {//当前行的所有列相加,然后求最长子序列和,记录最大值
                for(int k=1;k<=n;k++)
                    sumdata[k]+=data[j][k];

                int maxTemp=-100000;
                int sum = 0;
                for(int k=1;k<=n;k++)
                {
                    sum += sumdata[k];
                    maxTemp = max(sum,maxTemp);
                    sum = sum>0?sum:0;
                    
                }
                maxNum=max(maxNum,maxTemp);
            }
        }
        printf("%d\n",maxNum);
    }
    return 0;
}

ProID 1087 Super Jumping! Jumping! Jumping!

题目大意是求一个最长上升子序列,在上一篇文章中已经讲过类似的问题了,不过是用二分查找做的,因为这道题目是要求输出序列之和,就得老老实实用原方法了。

代码如下:

#include<iostream>
#include<string>
#include<cstdio>
#include<vector>
#include<cctype>
using namespace std;


int data[1001];
int dp[1001];
int tmp[1001];
int main()
{
    int n;
    while(scanf("%d",&n)!=EOF,n)
    {
        
        memset(dp,0,sizeof(dp));
        memset(tmp,0,sizeof(tmp));
        for(int i=0;i<n;i++)
            scanf("%d",&data[i]);
        dp[0]=data[0];
        tmp[0]=0;
        int sum=0;
        for(int i=1;i<n;i++)
        {
            dp[i]=data[i];
            for(int j=0;j<i;j++)
            {
                if(data[i]>data[j] && dp[i]<dp[j]+data[i])
                    dp[i]=dp[j]+data[i];
                sum=max(sum,dp[i]);
            }
        }
        printf("%d\n",sum);
    }
    return 0;
}


ProID 1114 Piggy-Bank

题目意思是给定一些硬币,有重量和价值,然后给一个一定重量的罐子,问在能否在装满罐子的条件下,猜到这个罐子的最小价值。这个是完全背包,在背包九讲中有详细的解释。也只是套个公式而已,需要注意初始化问题。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int t;
int dp[10005];
struct _data
{
    int a,b;
};
_data data[501];

int main()
{
    while(scanf("%d",&t)!=EOF)
    {
        int E,F,n;
        while(t--)
        {
            scanf("%d %d",&E,&F);
            scanf("%d",&n);
            memset(dp,0,sizeof(dp));
            bool flag=false;
            for(int i=0;i<n;i++)
            {
                scanf("%d %d",&data[i].a,&data[i].b);
                if(data[i].b<F-E)
                    flag=1;
            }
            if(!flag)
                printf("This is impossible.\n");
            else
            {
            	//初始化和01背包不一样
                for(int i=1;i<=F-E;i++)
                    dp[i]=5000000;
		//完全背包问题dp[i][j]表示考虑前i个物品在重量为j的情况下得到的最小价值
                for(int i=0;i<n;i++)
                {
                    for(int j=data[i].b;j<=F-E;j++)
                    {
                        dp[j]=min(dp[j],dp[j-data[i].b]+data[i].a);
                    }
                }
                
                if(dp[F-E]!=5000000)
                    printf("The minimum amount of money in the piggy-bank is %d.\n",dp[F-E]);
                else
                    printf("This is impossible.\n");
            }

        }
    }
    return 0;
}

ProID 1121 Complete the Sequence

题目大意是,给顶你一个符合P(n)=P(n) = aD.n^D+aD-1.n^D-1+...+a1.n+a0 多项式的序列,然后让你续填m个序列,说白了就是找出一个序列的规律来,就是按规律填数。

咋一看之下好像无从下手,a(*)怎么找啊,n如何确定?其实吧多项式相减就可以看出规律来,因为这个多项式是比较简单的相邻数相减就可消去高次方,然后再减可以消去下一个高次方。例子如下:

对于序列  1,2,3,4,7来说,

0 1 2 4 7
1 1 2 3 --
2 1 1 -- --
3 0 -- -- --

表格中描述的是3次相减之后的情况,总是可以在最后一步(也就是第n-1次相减中变为一个数字)现在来计算序列之后的数字,从下往回推:

3 0 0 -- -- --
2 1 1 1 -- --
1 1 2 3 4 --
0 1 2 4 7 11
来看看表格中第3行第3列的2是如何推导出来的,记住了差值是连续的两项的差,所以2=第二行第二列+第三行第2列的值,所以最后的11=第3行第5列的4+第四行第5列的7。

代码如下:

#include<iostream>
#include<cstdio>
#include<algorithm>

using namespace std;

int n,m,t,s[105][105];
int main()
{
    while(scanf("%d",&t)!=EOF)
    {
        while(t--)
        {
            scanf("%d %d",&n,&m);
            for(int i=0;i<n;i++)
                scanf("%d",&s[0][i]);
            for(int i=1;i<n;i++)//计算n阶差值
                for(int j=0;i+j<n;j++)
                    s[i][j]=s[i-1][j+1]-s[i-1][j];


            for(int i=1;i<=m;i++)
                s[n-1][i]=s[n-1][0];


		//还原过程
            for(int i=n-2;i>=0;i--)
                for(int j=0;j<m;j++)
                    s[i][n-i+j]=s[i][n-i+j-1]+s[i+1][n-i+j-1];
		//输出m需要求的值
            for(int i=0;i<m-1;i++)
                printf("%d ",s[0][n+i]);
            printf("%d\n",s[0][n+m-1]);
        }
        
    }return 0;
}

ProID  1158 Employment Planning

题目大意是老板要雇一群人工作,雇人和解雇都要付工资,工作的时候要付薪水而且不工作也要付(真好啊 )。问你帮忙计算出完成一项工作所需的最小花费。

这道题目很类似于前一篇文章中的星河战队问题,只不过这个比那道题目稍微简单一些,因为是线性的,不是树形的。

状态转移方程如下:

  Dp[i][j]为前i个月的留j个人的最优解;  其中 Num[i]<=j<=Max{Num[i]};  Num 数组表示的是当前这个月所需的最少人数,当然如果所雇用的人数绝对不超过所有月份中所多的人数,否则白白养那么一群人,老板也太好了。
对于Num[i]<=k<=Max_n,    当k<j时, 招聘;当k>j时, 解雇  然后求出最小值。Dp[i][j]=min{Dp[i-1][k…Max_n]+(招聘,解雇,工资);

代码如下:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

int dp[13][1005],num[13];

int n;
int hire,fire,salary;  
int main()
{
     while(scanf("%d",&n),n)
     {
         int MaxNum=0;
         scanf("%d%d%d",&hire,&salary,&fire);
	//如果说其中某个月所需要的人最多,那么总的人数不会超过这个,否则就是这个老板太慈善了。
         for(int i=0;i<n;i++)
         {
             scanf("%d",&num[i]);
             MaxNum=max(MaxNum,num[i]);
         }
         memset(dp,0,sizeof(dp));

         for(int i=num[0];i<=MaxNum;i++)
         {
             dp[1][i]=i*(salary+hire); //hire 也要加上
         }
         for(int i=2;i<=n;i++)
         {
             for(int j=num[i-1];j<=MaxNum;j++)
             {
                  int Min=0xffffff;
                  for(int k=num[i-2];k<=MaxNum;k++)//上个月雇佣的人数
                  {
		//当k<j时, 招聘, 当k>j时, 解雇,  然后求出最小值
                      if(k>j)
                          Min=min(Min,dp[i-1][k]+(k-j)*fire+j*salary);
                      else 
                          Min=min(Min,dp[i-1][k]+(j-k)*hire+j*salary);
                  }
                  dp[i][j]=Min;
             }
         }
         int ans=0xffffff;
          for(int i=num[n-1];i<=MaxNum;i++) 
              ans=min(ans,dp[n][i]);
          printf("%d\n",ans);
     }
     return 0;
}


你可能感兴趣的:(杭电60道DP问题总结(二))