题目链接:https://codeforces.com/gym/102028/problem/B
题目描述:
奥特曼打怪兽,有怪兽 a a a和 b b b 分别对应的生命值为 H P a , H P b HP_a,HP_b HPa,HPb和攻击力 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;
}