2018-2019 ACM-ICPC, Asia Jiaozuo Regional Contest B - Ultraman vs. Aodzilla and Bodzilla 贪心

题目链接:https://codeforces.com/gym/102028/problem/B

题目描述:

奥特曼打怪兽,有怪兽 a a a b b b 分别对应的生命值为 H P a , H P b HP_a,HP_b HPaHPb和攻击力 A T K a , A T K b ATK_a,ATK_b ATKa,ATKb,按回合制进行攻击,如果怪物活着,将对奥特曼先造成对应的伤害,在第 i i i回合奥特曼打怪的伤害为 i i i,问奥特曼杀死这两只怪兽后受到的最小伤害,并对应输出字典序最小的方案

题解思路:

在贪心上的最优方案一定是优先杀一个人,再杀另一个。
f ( x ) f(x) f(x)表示对一个怪兽造成 x x x点伤害的最小步数,就是等差数列求个和。
那么我们一定可以在 f ( H P a + H P b ) f(HP_a+HP_b) f(HPa+HPb)的时间将两者全部杀掉。
假设先杀 a a a我们总可以保证在 f ( H P a ) f(HP_a) f(HPa)的时间杀死 a a a并且不浪费一点攻击力,即在 [ 1 , f ( H P a ) ] [1,f(HP_a)] [1,f(HPa)]的时间穿插着打 b b b,并且杀死 a a a

接下来分两种策略,分别是先杀a和先杀b。
先用三次二分搜索找出 l e n , l e n 1 , l e n 2 len,len1,len2 len,len1,len2分别对应杀光,杀 a a a,杀 b b b对应的最小步数。

如果先杀 a a a的话,就是在 [ 1 , l e n 1 ] [1,len1] [1,len1]穿插打 b b b,并打死 a a a ( l e n 1 , l e n ] (len1,len] (len1,len]全部打 b b b
现在的问题就是如何分配打 b b b,保证这个字符串字典序最小。
现在有一个 n e d ned ned l e f lef lef表示还需要多少打 b b b(可能不需要),和打 a a a的部分可以给多少出去。
我们就倒序在 [ 1 , l e n 1 ] [1,len1] [1,len1]找,如果需要,并且有剩余,就替换为 B B B

第二种情况是先杀 b b b的策略,这种策略在 [ 1 , l e n 2 ] [1,len2] [1,len2]分配打法的时候思维上比较复杂= =
在贪心策略上,依旧考虑 n e d ned ned l e f lef lef
顺序循环填 A A A的情况会出现当前如果填了 A A A之后后面无法再填A( l e f lef lef不够了),这个时候就需要移动一下填 A A A的位置到需要的地方。这种情况在第一个策略里面就不需要考虑,因为倒序循环不会出现这种情况。

#include <bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define ll long long
#define int ll
#define debug cout<<"fuck"<<endl;
const int mod=(int)1e9+7;
const int maxn=(int)3e5+5;
int a[maxn];
int len,len1,len2;
int n,t;
int h1,h2,a1,a2,hur1,hur2;

bool check(int x,int ned)
{
    ll sum=(x+1)*x/2;
    if(sum<ned)return 0;
    else return 1;
}
int sum(int l,int r)
{
    return (r-l+1)*(l+r)/2;
}

int cmp(string a,string b)
{
    for(int i=1;i<min(a.size(),b.size());i++)
    {
        if(a[i]==b[i])continue;
        else return a[i]>b[i]?1:2;
    }
    return a.size()>=b.size()?1:2;
}

int binary_search(int x)
{
    int l=1,r=(int)1e9,mid;
    while(l<r)
    {
        mid=(l+r)/2;
        if(check(mid,x))r=mid;
        else
        {
            l=mid+1;
        }
    }
    return l;
}

string kill_a()
{
    string s="";
    s+='0';
    int ned=h2;
    int lef=sum(1,len1)-h1;
    for(int i=1;i<=len;i++)
    {
        if(i<=len1)s+='A';
        else
        {
            s+='B';
            ned-=i;
        }
    }
    for(int i=len1;i>=1&&ned>0;i--)
    {
        if(lef>=i)//如果需要,并且有剩余
        {
            lef-=i;
            ned-=i;
            s[i]='B';
        }
    }
    return s;
}

string kill_b()
{
    string s="";
    s+='0';
    int lef=0;
    int ned=h1-sum(len2+1,len);
    for(int i=1;i<=len;i++)
    {
        if(i<=len2)
        {
            s+='B';
            lef+=i;
        }
        else s+='A';
    }
    lef-=h2;
    for(int i=1;i<=len2&&lef>0;i++)
    {
        if(lef>=i)
        {
            if(lef-i<i+1&&ned-i>0)//如果这个位置修改为A之后,之后不能再修改了,但是还有需要
            {
                s[ned]='A';
                //cout<<"i:"<<i<<' '<<"ned:"<<ned<<endl;
                break;
            }
            lef-=i;
            ned-=i;
            s[i]='A';
        }
    }


    return s;
}

void sout(string s)
{
    for(int i=1;i<s.size();i++)
    {
        cout<<s[i];
    }
    cout<<endl;
}

signed main()
{
    IOS
    cin>>t;

    while(t--)
    {
        cin>>h1>>h2>>a1>>a2;
        len=binary_search(h1+h2);
        len1=binary_search(h1);
        len2=binary_search(h2);
        hur1=len1*(a1+a2)+(len-len1)*a2;
        hur2=len2*(a1+a2)+(len-len2)*a1;

        string fir_kil_a=kill_a();
        string fir_kil_b=kill_b();

        //cout<<hur1<<' '<<fir_kil_a<<endl;
        //cout<<hur2<<' '<<fir_kil_b<<endl;

        if(hur1<hur2)
        {
            cout<<hur1<<' ';
            sout(fir_kil_a);
        }
        else if(hur1>hur2)
        {
            cout<<hur2<<' ';
            sout(fir_kil_b);
        }
        else
        {
            if(cmp(fir_kil_a,fir_kil_b)==1)//a大
            {
                cout<<hur2<<' ';
                sout(fir_kil_b);
            }
            else
            {
                cout<<hur1<<' ';
                sout(fir_kil_a);
            }
        }
    }


    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值