动态规划(入门,滚动数组,记录的都是状态):SWUSTACM-1010 魔兽争霸之最后的反击

题目:

                                                                                    1010: 魔兽争霸之最后的反击
                                                                        Time Limit: 1000 MS Memory Limit: 65536 KB
Description
相传人族与兽族对峙了很久,双方均受到了重创,兽族趁人类没有能力发起大规模进攻之时突然袭击,想一次彻底打败人族。人类为了生存,无论老幼伤病,全部参战,兵分两路抗敌。
由于体质不同,我们以血量表示一个人的战斗力,现在给你所有人的血量,请你把人类分成战斗力最接近的两部分。注意,战斗力要最接近,不然,人族会因你而战败呦!

Input
第一行为一个整数n(1<=n<=36),表示人族的总人数。以下的n行每行一个整数,表示一个人的血量mi(即战斗力),其中1<=mi<=400.


Output
只有一行,包含两个数,即人族的每部分兵的血量总和,较小的一个值放在前面,两个数用空格分隔。


Sample Input
3
20
32
35


Sample Output
35 52


Source
SWUST



解题心得:

1、可以直接价将最大的战斗值/2,然后动态规划就行了。在规划的过程中状态有几种转移的方法。注意此题记录的都是状态(只有false和true),因为记录的是状态所以开的数组直接是bool类型就可以了,第一维是记录的物品的个数,第二维记录的是在选择第k个物品(人族的群数)的时候有多少种可能的重量(战斗力)。当可以达到这个重量的时候将这个重量记录为true。


第一种:

就是很简单的记录人群的数量和总的战斗力,直接给代码.

关于核心的动态规划解释一下:第k件物品,可以选择不装入,所以选择第k件物品的重量就是和第k-1个相同物品的状态相同。也可以选择装入,装入就必须第前一个状态【k-1】的 j-t[j] 要是true才可以转移到这个状态。

#include<bits/stdc++.h>
using namespace std;
bool dp[40][16000];//只是记录的状态bool就可以了,甚至bitset也可以;
int t[40];
int main()
{
    int n;
    int sum ;
    while(~scanf("%d",&n))
    {
        memset(dp,0,sizeof(dp));
        sum = 0;
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&t[i]);
            sum += t[i];
        }
        dp[0][0] = 1;
        for(int i=1;i<=n;i++)
        {
            for(int j=sum;j>=0;j--)
            {
                for(int k=1;k<=n;k++)
                    dp[k][j] = dp[k-1][j] | dp[k-1][j-t[k]];//这个是核心算法
            }
        }
        for(int i=sum/2;i>=0;i--)
        {
            if(dp[n][i])
            {
                printf("%d %d
",i,sum-i);
                break;
            }
        }
    }
}


第二种:使用滚动数组优化
因为只有前后的状态转移所以数组的第一维只需要开2就可以了,因为true都不变(可以选择不放入),所以状态的转移是将下一个的false在上一个的true之上改变为true。说不清楚了,直接贴代码。
#include<bits/stdc++.h>
using namespace std;
bool dp[2][16000];
int t[40];
int main()
{
    int n;
    int sum ;
    while(~scanf("%d",&n))
    {
        memset(dp,0,sizeof(dp));
        sum = 0;
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&t[i]);
            sum += t[i];
        }
        dp[0][0] = 1;
	int now;
        for(int i=1;i<=n;i++)
        {
            for(int j=sum;j>=0;j--)
            {
                if(j >= t[i])
                    dp[i%2][j] = dp[(i+1)%2][j] | dp[(i+1)%2][j-t[i]];
                else
                {
                    if(dp[(i+1)%2][j])
                        dp[i%2][j] = 1;
                }
            }
            now = i;//用来记录最后的那个数组,写的好弱智;
        }
        for(int i=sum/2;i>=0;i--)
        {
            if(dp[now%2][i])
            {
                printf("%d %d
",i,sum-i);
                break;
            }
        }
    }
}

第三种:开一个一维数组直接转移就可以了

其实思想也很简单,这个状态转移不是从前面一次到后面转移,而是从后面往前面转移,所以后面的状态不会影响前面的状态,直接转移就可以了。

#include<bits/stdc++.h>
using namespace std;
bool dp[16000];
int t[40];
int main()
{
    int n;
    int sum ;
    while(~scanf("%d",&n))
    {
        memset(dp,0,sizeof(dp));
        sum = 0;
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&t[i]);
            sum += t[i];
        }
        dp[0] = 1;
  	for(int i=1;i<=n;i++)
            for(int j=sum;j>=0;j--)
            {
                dp[j] = dp[j] | dp[j-t[i]];
            }
        for(int i=sum/2;i>=0;i--)
        {
            if(dp[i])
            {
                printf("%d %d
",i,sum-i);
                break;
            }
        }
    }
}



原文地址:https://www.cnblogs.com/GoldenFingers/p/9107364.html