VIJOS P1153 猫狗大战

VIJOS P1153 猫狗大战 解题报告

题目链接

https://vijos.org/p/1153

题目描述

PS :只有最后一段有用

新一年度的猫狗大战通过SC(星际争霸)这款经典的游戏来较量,野猫和飞狗这对冤家为此已经准备好久了,为了使战争更有难度和戏剧性,双方约定只能选择Terran(人族)并且只能造机枪兵。
比赛开始了,很快,野猫已经攒足几队机枪兵,试探性的发动进攻;然而,飞狗的机枪兵个数也已经不少了。野猫和飞狗的兵在飞狗的家门口相遇了,于是,便有一场腥风血雨和阵阵惨叫声。由于是在飞狗的家门口,飞狗的兵补给会很快,野猫看敌不过,决定撤退。这时飞狗的兵力也不足够多,所以没追出来。
由于不允许造医生,机枪兵没办法补血。受伤的兵只好忍了。555-

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

1)两部分兵的个数最多只能差一个
2)每部分兵的血值总和必须要尽可能接近。
现在请你编写一个程序,给定野猫现在有的兵的个数以及每个兵的血格值,求出野猫按上述规则分成两部分后每部分兵的血值总和

输入格式

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

输出格式

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

样例输入

3
35
20
32

样例输出

35 52

题解

这道题在题目中除了废话之外已经将算法透露了一大部分,从“最多只能差一个”,“接近”,这类字眼中我们可以很容易的发现,这是一个

DP

既然是dp的题目,那么重要的还是状态转移方程究竟是什么

就是01背包模板

但由于题目的限制,我们可以对它进行一些优化
由于强调了 :“两边的两部分兵的个数最多只能差一个”,
所以我们只需要从所给人数的一半(n/2)循环即可;
同理,由要求:“每部分兵的血值总和必须要尽可能接近”,
也可以推出从总和的一半(sum/2)循环即可。、

这样我们便节约了一部分空间与时间。

C++代码

#include<iostream> 
#include<cstdio>
using namespace std;
int n,a[210],sum;
bool dp[210][8005];//dp[放入的士兵数][可达到的值] ,bool数组节约空间
int main()
{
    scanf(“%d”,&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        sum+=a[i];
    }
    dp[0][0]=true;//dp初始化
    //01背包模板

    for(int i=1;i<=n;i++)
        for(int j=n/2;j>=0;j--)//优化
            for(int k=sum/2;k>=0;k--)
            {
                if(dp[j][k])
                    dp[j+1][k+a[i]]=true;

    for(int i=sum/2;i>=0;i--)
        if(dp[n/2][i])
        {
            printf("%d %d",i,sum-i);
            break;
        }
    return 0;
}

结果如下

我们获得了

但能不能更优化一点呢?

能的

重新读题,

1)两部分兵的个数最多只能差一个;
2)每部分兵的血值总和必须要尽可能接近。
现在请你编写一个程序,给定野猫现在有的兵的个数以及每个兵的血格值,求出野猫按上述规则分成两部分后每部分兵的血值总和。

我们发现其实这里面也隐藏着另外一种思想

贪心

为什么这么想是因为数据原因,n只是到200,血量最大值也只是40而已(即两者之间的差距不会过大,进行尝试都可以),这决定了我们可以使用贪心来解决问题

接下来就是怎么贪了:

我们不妨先强行分组,在进行交换士兵,条件就是可以使两队之间的差变小

C++代码

#include  <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;
int a[210],b[210];
int suma,sumb,MIN,n;
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n/2;i++)
    {
        scanf("%d",&a[i]);
        suma+=a[i];
    }
    for(int i=1;i<=n-n/2;i++)
    {
        scanf("%d",&b[i]);
        sumb+=b[i];
    }
    //强行进行分组
    MIN=abs(suma-sumb);
    for(int i=1;i<=n/2;i++)
        for(int j=1;j<=n-n/2;j++)
        {
            if(abs(sumb-suma+2*(a[i]-b[j]))<MIN)//如果交换会将差变小
            {
                suma+=b[j]-a[i];
                sumb+=a[i]-b[j];
                MIN=abs(suma-sumb);
                swap(a[i],b[j]);//交换
            }
        }
    printf("%d %d",min(suma,sumb),max(suma,sumb));
    return 0;
}

时间大优

这里写图片描述

上面也提到了,交换次数少,所以这道题

随机化

也可已解决,自己探讨吧。

题目传送门

https://vijos.org/p/1153

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值