BZOJ 1506 双塔问题

目录

一.题目:

题目描述:

输入描述:

输出描述:

样例输入:

样例输出:

二.题解:

     一.思路:

     二.代码实践

     三.样例代码

三.总结


一.题目:

题目描述:

      2001年9月11日,一场突发的灾难将纽约世界贸易中心大厦夷为平地,Mr. F曾亲眼目睹了这次灾难。为了纪念"9?11"事件,Mr. F决定自己用水晶来搭建一座双塔。 Mr. F有N块水晶,每块水晶有一个高度,他想用这N块水晶搭建两座有同样高度的塔,使他们成为一座双塔,Mr. F可以从这N块水晶中任取M(1≤M≤N)块来搭建。但是他不知道能否使两座塔有同样的高度,也不知道如果能搭建成一座双塔,这座双塔的最大高度是多少。所以他来请你帮忙。 给定水晶的数量N(1≤N≤100)和每块水晶的高度Hi(N块水晶高度的总和不超过2000),你的任务是判断Mr. F能否用这些水晶搭建成一座双塔(两座塔有同样的高度),如果能,则输出所能搭建的双塔的最大高度,否则输出"Impossible"。

输入描述:

      输入的第一行为一个数N,表示水晶的数量。第二行为N个数,第i个数表示第i个水晶的高度。

输出描述:

      输出仅包含一行,如果能搭成一座双塔,则输出双塔的最大高度,否则输出一个字符串"Impossible"。

样例输入:

      5

      1 3 4 5 2

样例输出:

      7


二.题解:

 


   一.思路:

      这道题是一道经典的DP,主要用的思路是填表法,也就是顺推的思路。我们首先定义dp数组,把这个数组定义成一个二维数组,即dp[i][j],表示放了第i个水晶后,两个塔的高度相差j,其中最高的那一个塔。也就是说,i表示水晶块数,j表示两塔高度之差,dp[i][j]存其中最高的那一个塔的高度。那么,每此放水晶都有四种情况:

     


1.把这块水晶放在高的那一个塔上:

            那么高的那一个塔就更高,j是现在放上去之后两塔的高度之差,那么原来两塔的高度之差就应该是:j - h[i],于是状态转移方程就是:dp[i][j] = max {dp[i][j], dp[i - 1][j - h[i]] + h[i]}。加h[i],是因为最高的塔变高了。

            如图:

     


2.把这块水晶放在矮的那一个塔上,且放上去之后,矮的那一个塔仍然矮:

            由此就可以推出,两个塔原来的高度差就是:j + h[i],于是状态转移方程式就是:dp[i][j] = max(dp[i][j], dp[i - 1][j + h[i]])

            如图:

   


  3.把这块水晶放在矮的那一个塔上,且放上去之后,矮的那一个塔变得比原本高的塔还高了:

            同理,两个塔的原高度差是:h[i] - j,状态转移方程就变成了:dp[i][j] = max (dp[i][j], dp[i - 1][h[i] - j] + j),加j是因为最高的那个塔变成了原本矮的那个塔,而矮的那个塔又是原最高塔加上现在的两塔高度之差。

            如图:

     


4.最后就剩一种情况,自然就是不放这块水晶,状态转移方程:dp[i][j] = max (dp[i][j], dp[i - 1][j]);

      这道题就完了,最后只需输出用了n块水晶之后,两塔高度差为0时,其中一塔高度。

     


二.代码实践

      既然思路都知道了,那么代码实践就十分简单了。只需读入之后就进行一个二重循环的DP,最后输出就行了。只不过要注意,dp数组是需要初始化的,全部附为极小值,十分玄学,大家可以讨论一下为什么。

     


三.样例代码

#include <cstdio>
#include <iostream>
#include <cstring>
#define M 105
#define max(a, b) a > b ? a : b
const int MIN = -0x3f3f3f3f;//附极小值
using namespace std;

int n, h[M], dp[M][2005], sum;

int main (){
    scanf("%d", &n);
    for(int i = 1; i <= n; i ++){
        scanf("%d", &h[i]);
        sum += h[i];
    }

    memset(dp, MIN, sizeof(dp));//玄学初始化
    dp[0][0] = 0;//注意

    for(int i = 1; i <= n; i ++){
        for(int j = sum; j >= 0; j --){
            if(j >= h[i])//情况一
                dp[i][j] = max (dp[i][j], dp[i - 1][j - h[i]] + h[i]);
            dp[i][j] = max (dp[i][j], dp[i - 1][j + h[i]]);//情况二
            if(j <= h[i])//情况三
                dp[i][j] = max (dp[i][j], dp[i - 1][h[i] - j] + j);
            dp[i][j] = max (dp[i][j], dp[i - 1][j]);//情况四

        }
    }

    if(!dp[n][0])//输出要注意判断无法组成双塔的情况
        printf("Impossible\n");
    else
        printf("%d\n", dp[n][0]);
    return 0;
}

三.总结

      这道动态规划十分的经典,主要是对于四种情况的考虑。其实我是在草稿纸上写写画画,搞出了这些情况,没想到就对了。所以,做题的时候光有聪明的头脑是远远不够的,草稿纸十分重要,特别是对于DP题,需要推出状态转移方程式。加油吧,DP总是会越做越有经验的!谢谢!

猜你喜欢

转载自blog.csdn.net/weixin_43908980/article/details/84760534