洛谷 P1489 猫狗大战【背包+bool类型dp】

原题链接:https://www.luogu.com.cn/problem/P1489

题目描述

新一年度的猫狗大战通过 SC(星际争霸)这款经典的游戏来较量,野猫和飞狗这对冤家为此已经准备好久了,为了使战争更有难度和戏剧性,双方约定只能选择 Terran(人族)并且只能造机枪兵。

比赛开始了,很快,野猫已经攒足几队机枪兵,试探性的发动进攻;然而,飞狗的机枪兵个数也已经不少了。野猫和飞狗的兵在飞狗的家门口相遇了,于是,便有一场腥风血雨和阵阵惨叫声。由于是在飞狗的家门口,飞狗的兵补给会很快,野猫看敌不过,决定撤退。这时飞狗的兵力也不足够多,所以没追出来。

由于不允许造医生,机枪兵没办法补血。受伤的兵只好忍了。

现在,野猫又攒足了足够的兵力,决定发起第二次进攻。为了使这次进攻给狗狗造成更大的打击,野猫决定把现有的兵分成两部分,从两路进攻。由于有些兵在第一次战斗中受伤了,为了使两部分的兵实力平均些,分的规则是这样的:

  1. 两部分兵的个数最多只能差一个;
  2. 每部分兵的血值总和必须要尽可能接近。

现在请你编写一个程序,给定野猫现在有的兵的个数以及每个兵的血格值,求出野猫按上述规则分成两部分后每部分兵的血值总和。

输入格式

第一行为一个整数 n (1≤n≤200),表示野猫现在有的机枪兵的个数。以下的 n 行每行一个整数,表示每个机枪兵的血格 (1≤ai​≤40)。

输出格式

一行,为两个整数,表示分成两部分后每部分兵的血值总和。要求输出的第一部分兵的血量总和不大于第二部分兵的血量总和。

输入输出样例

输入 #1

3
35
20
32

输出 #1

35 52

说明/提示

TO 狗狗:这道题的数据范围我已经尽量按星际的游戏规则来了,如果你再固执于由于机枪兵的攻击力一定使不能达到某些血格值或者游戏中一定要造农民不能使机枪兵的人数达到 200 的话,我只能决定将那场猫狗大战的录像公开于世人了!!!

解题思路:

首先题目说了俩部分的兵的个数最多只能差1。

当兵的个数为偶数时,分为俩部分俩部分的兵要么相等要么至少相差2(并且俩部分的兵数相差只会是偶数),所以俩部分兵个数相差不能为1,只能相等。

当兵的个数为奇数时,俩部分的兵不可能相等,至少相差1(并且相差兵的个数一定是奇数),所以俩部分兵的个数在这个题目只能相差1。

综上所述我们可以得出一个结论

(1)当兵的个数是偶数时,俩部分的兵的个数只能相等

(2)当兵的个数是奇数时,俩部分的兵的个数只能相差1

初始时兵的数量为n,当n为偶数时,一部分兵的数量为n/2,另一部分兵的数量也为n/2

当n为奇数时,一部分兵的数量为n/2,另一部分兵的数量为n/2+1。

从上述分析可以知道这俩部分必定有一部分兵的数量为n/2,我们令m=n/2,然后我们可以用sum来记录所有的n个兵的总的血量,我们只要知道其中一部分的血量blood,那么另一部分的血量就可以通过sum-blood算出来,所以我们只需要处理其中一部分的血量即可。

由于不管考虑那部分,另一部分都是对称的,所以我们只需要考虑其中一部分即可,这里我们可以考虑兵的个数为m=n/2这一部分兵的血量。

到这里我们已经满足了第一个条件俩部分兵的个数最多相差1,接下需要考虑的就是怎么让俩部分的血量最接近,最简单的办法我们可以枚举m=n/2这一部分,也就是从n个物品中选择m个物品血量可以为哪些值,看到这句话我们就可以看出来这不就是背包问题了吗,下面使用背包进行处理。

状态定义:

定义:f[i][j][k]表示从前i个物品中选,并且刚好选择了j个物品,并且血量刚好为k的选法是否存在。

初始化:

f[0][0][0]=true,  从前0个物品中选只能选择0个物品,并且血量只能为0

状态转移

不选择第i个物品

f[i][j][k] = f[i][j][k] || f[i - 1][j][k];

选择第i个物品

f[i][j][k] = f[i][j][k] || f[i - 1][j - 1][k - a[i]];

最终答案

枚举所有的f[n][m][k] (0<=k<=m*40),看从前n个物品中选择m个物品并且血量刚好为k的情况是否存在,如果存在,那么其中一部分血量为k,另外一部分的血量为sum-k,对所有存在的情况取一个最接近的值即可。

时间复杂度:首先第一维枚举所有物品时间为O(n),第二维枚举选的物品数时间为O(m),第三位枚举血量时间为O(m*40),n=200,m=100,所以时间为200*100*100*40=8e7,这个时间复杂度是有点高,但是常数非常小,是可以过的。

空间复杂度:这个题目看起来空间要搞比较高,再来分析一下空间复杂度,空间为n*m*m*40,大概需要80M,这个题目给了128M,所以空间复杂度是足够的。

cpp代码如下

#include 
#include 
#include 
#include 

using namespace std;

const int N = 210, M = 4010;

int n, m;
int a[N];
bool f[N][110][M];
int main()
{
    cin >> n;
    int sum = 0;  //sum记录所有兵的总血量
    for (int i = 1; i <= n; i++)
        scanf("%d", &a[i]), sum += a[i];

    m = n / 2;  //其中一部分的兵的个数
    f[0][0][0] = true;  //初始化
    for (int i = 1; i <= n; i++)
        for (int j = 0; j <= i && j <= m; j++)  //最多只能选择m个物品,
            for (int k = 0; k <= j * 40; k++)   
            {
                //不选择当前物品
                f[i][j][k] = f[i][j][k] || f[i - 1][j][k];
                //选择当前物品
                if (k >= a[i] && j >= 1)
                    f[i][j][k] = f[i][j][k] || f[i - 1][j - 1][k - a[i]];
            }

    //记录答案
    int diff = 1e9, ans1, ans2;
    for (int k = 0; k <= m * 40; k++)
        if (f[n][m][k])
        {
            int blood1 = k, blood2 = sum - k;
            if (abs(blood1 - blood2) < diff)
            {
                diff = abs(blood1 - blood2);
                ans1 = min(blood1, blood2);
                ans2 = max(blood1, blood2);
            }
        }
    cout << ans1 << ' ' << ans2 << endl;
    return 0;
}

你可能感兴趣的:(动态规划,算法,动态规划)