【数学思维】【分类】codeforces102028B Ultraman vs. Aodzilla and Bodzilla

B. Ultraman vs. Aodzilla and Bodzilla
time limit per test2.0 s
memory limit per test1024 MB
inputstandard input
outputstandard output
Six months ago our hero, formerly known as Huriyyah, was beating all monsters in the land. Now he changed his name to Ultraman and left his beloved land. He is ready to take on a new challenge.

In a remote land, local citizens are suffering from the harassment of two powerful and horrible monsters: Aodzilla and Bodzilla. They eat small children who go out alone and even kill innocent persons. The apprehension of being attacked has overwhelmed people for several decades.

For the good of these unfortunate citizens, Ultraman sets out for the forest which is the main lair of Aodzilla and Bodzilla. In the forest, he faces these two fierce and cruel monsters and fights with them. The health points of Aodzilla and Bodzilla are HPA and HPB respectively, and their attack values are ATKA and ATKB respectively.

They fight in a cave through turn-based battles. During each second, the Ultraman will be attacked by monsters at first, and the damage is the sum of attack values of all alive monsters. Then he will select exactly one monster which is still alive and attack it. The selected monster will suffer a damage of value i (i.e. its health point will be decreased by i) where i represents that Ultraman has launched i attacks in total, from the beginning to the present, to these two monsters (and the current attack is the i-th one). That is to say, during the 1-st second, one of these two monsters will be under an attack of damage 1, during the 2-nd second, one of them, if alive, will be under an attack of damage 2, during the 3-rd second, one of them, if alive, will be under an attack of damage 3, and so on. If at some time, the health point of a monster is less than or equal to zero, it will die immediately. The Ultraman will win if both monsters have been killed.

Now, you are asked to develop a strategy to minimize the total damage Ultraman should suffer before he wins the battle. A strategy can be described as a string whose length is the total time that the battle will last. The i-th character in the string is ‘A’, if the Ultraman chooses to attack Aodzilla during the i-th second; otherwise, the i-th character is ‘B’, which means Bodzilla will be the target during that second. You are also asked to find the optimal strategy whose string description is the smallest in lexicographical order among all possible optimal strategies.

For two distinct strings s and t, if one string is a prefix of the other, then the one with a shorter length is smaller in lexicographical order. In other cases, s is smaller than t in lexicographical order if the first character of s is smaller than the first character of t, or in case they are equivalent, the second character of s is smaller than the second character of t, etc. The case t is smaller than s in lexicographical order is defined similarly as the former case.

Input
The input contains several test cases, and the first line contains a positive integer T indicating the number of test cases which is up to 105.

For each test case, the only one line contains four integers HPA, HPB, ATKA and ATKB, where 1≤HPA,HPB,ATKA,ATKB≤109.

We guarantee that there are at most 100 test cases with max{HPA,HPB}>103.

Output
For each test case, output a line containing an integer indicating the minimal total damage Ultraman should suffer, and a string describing the optimal strategy such that the string description is the smallest in lexicographical order among all possible optimal strategies. You should output exactly one whitespace between the number and the string.

Example
input
2
5 15 5 25
5 15 25 5
output
155 BBBBBA
105 AAABBB


 今天和LPY模拟了焦作的ICPC区域赛,写了5道水题,难题没啥想法,很尴尬,大概是靠罚时拿银牌的水平。唉,以后要尽快提升实力了。
 这题是焦作赛区B题。当时是有想法但没写出来,其实按理来说不难,但思维强度确实不小。另外,以后这种题,一定要在纸上演算,理清楚思路,不要乱来。
 我先打了个暴力,直观感受了一下,没有感觉到特殊的地方。

#include<cstdio>
#include<string>
#include<iostream>
using namespace std;

int ans=1E9,aa,bb,ha,hb;
string f;

void dfs(int x, int cura, int curb, int res, string s)
{
	if(cura<=0&&curb<=0)
	{
		if(res<ans)
			ans=res,f=s;
		return ;
	}
	res+=(cura>0?aa:0)+(curb>0?bb:0);
	if(cura>0)
		dfs(x+1,cura-x,curb,res,s+'A');
	if(curb>0)
		dfs(x+1,cura,curb-x,res,s+'B');
}

int main()
{
	freopen("B.in","r",stdin);
	freopen("ttmp.out","w",stdout);
	int T;
	scanf("%d",&T);
	while(T--)
	{
		ans=1E9;
		scanf("%d%d%d%d",&ha,&hb,&aa,&bb);
		dfs(1,ha,hb,0,"");
		printf("%d ",ans);
		cout<<f<<endl;
	}
	return 0;
}

 后来有了点想法,首先假设A先被我打死,那么假设是第X步打死,那么之前X步A就一直在攻击,接下来只有B在攻击。我们枚举这个X,然后去求B死去的最早时刻。怎么求呢?1–>X不一定都是攻击A的,有些攻击会分配给B,但是分配之后要保证A被打死,可以证明,能做到1–>X中取一部分,恰好能杀死A(可以用贪心法证明),剩余的就分配给B,这样第X步起,B的血量就最小了,持续攻击B就行了。
 当然,B先被我们打死的情况也要考虑,两大类取最小值。
 问题落在了怎么求方案上。


 暂存,以上可能有误
 非常不好意思,最近太忙,这篇博文今天补充完毕。


https://codeforces.com/blog/entry/63729 在唐老师的blog上看到了一点提示。首先求最优解可以有更简单的方法。定义f(x)为第一个满足sta~y的和不小于x的y,那么游戏结束的时间最少一定是f(ha+hb),原因在于在1–>f(ha+hb)中,一定能挑出来一部分步数,凑出ha,剩余的攻击分配给hb,因此方案存在,同时,不可能存在时间更短的方案了。在这种情况下,我们也可以知道,a至少在f(a)死去,b至少在f(b)死去。在总时间最少是f(ha+hb)的情况下,我们发现,可以做到a在f(a)死去或者b在f(b)死去(注意,这并不意味着前f(a)都攻击a,或者前f(b)都攻击b),显然最优情况在这两种情况之中,取最小值即可。这样用二分法或者解方程都可以,不用像之前那么逐个枚举了。
  好,现在问题回到求方案上。唐老师的blog中只是非常模糊地提到了贪心。然而我尝试了几种贪法都GG了。最后想出了这么个解决方法(非正解)。
  问题可以转化为:从1–>x中挑选一部分给A,和要满足在[L,R]之间,这个上下界是由恰好让ha变成0和恰好让hb变成0两种极端情况决定的(可以思考),同时满足字典序最小。
  不妨从左到右逐一考虑是否能让某位置pos为A,能的话就变成A。那么如何检查可行性呢?实际上就是问剩下的pos+1–>x能否找出一部分数字,和在一个上下界[L’,R’]之间。我们观察到,考虑从这段连续数字中挑选1个,2个,3个……挑选i个,则最小值p是前面i个数字之和,最大值q是后面i个数字之和,两者之间所有的数都可以被其中i个数字表示。倘若存在某个i,使得[L’,R’]区间与[p,q]区间有交,那么当然存在可行方案。否则可行方案不存在,pos不能填写A。
  直接这么写还会T,但可以用二分,找到第一个不小于L’的q,然后检查下p是否比R大就OK了。单组数据效率大致是O(sqrt(h)*log(h))。
 最后注意一下,如果a先死和b先死的最优值相等,求字符串的时候我们都要考虑,然后找出字典序更小的那个。之前单纯只考虑了一种情况,WA了好几发。

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
using LL=long long;

LL T,ha,hb,aa,ab,x,y,z,L,R;
char s1[100005],s2[100005];

inline LL cal(LL x, LL y)
{
	return (x+y)*(y-x+1)/2;
}

LL f(LL x)
{
	LL L=0,R=1E9,M;
	while(L<R)
	{
		M=L+R>>1;
		if(cal(1,M)<x)
			L=M+1;
		else
			R=M;
	}
	return L;
}

LL get_sta(int sp, int ep, int limit)
{
	LL L=sp,R=ep;
	while(L<R)
	{
		int M=L+R+1>>1;
		if(cal(M,ep)<limit)
			R=M-1;
		else
			L=M;
	}
	return L;
}

bool check(LL sum, LL pos, LL x, LL L, LL R)
{
	if(sum+pos>R)
		return false;
	L-=sum+pos;
	R-=sum+pos;
	if(L<=0)
		return true;
	LL sta=get_sta(pos+1,x,L),q=cal(sta,x),p=cal(pos+1,x-sta+1+pos);
	return p<=R;
}

void greedy(char *s, LL L, LL R, LL x, LL z, char c)
{
	LL sum=0;
	for(LL i=1;i<=x;i++)
		if(check(sum,i,x,L,R))
			s[i]='A',sum+=i;
		else
			s[i]='B';
	for(LL i=x+1;i<=z;i++)
		s[i]=c;
	s[z+1]='\0';
}

int main()
{
	scanf("%lld",&T);
	while(T--)
	{
		scanf("%lld%lld%lld%lld",&ha,&hb,&aa,&ab);
		x=f(ha);
		y=f(hb);
		z=f(ha+hb);
		if(z*ab+x*aa<=z*aa+y*ab)
		{
			R=cal(1,z)-hb;
			L=ha;
			greedy(s1,L,R,x,z,'B');
			if(z*ab+x*aa<z*aa+y*ab)
				printf("%lld ",z*ab+x*aa),puts(s1+1);
		}
		if(z*ab+x*aa>=z*aa+y*ab)
		{
			L=ha-(cal(1,z)-cal(1,y));
			R=cal(1,y)-hb;
			greedy(s2,L,R,y,z,'A');
			if(z*ab+x*aa>z*aa+y*ab)
				printf("%lld ",z*aa+y*ab),puts(s2+1);
		}
		if(z*ab+x*aa==z*aa+y*ab)
			printf("%lld ",z*aa+y*ab),puts(strcmp(s1+1,s2+1)>0?s2+1:s1+1);
	}
	return 0;
}

 这题花了自己这么久,说到底还是自己太菜鸡的缘故。以后还是要在纸上好好算,好好想,努力提高能力,要不然明年该怎么打比赛呀。

 最后,在想这题的过程中,我发现了一个和这题并不太相关的结论,但是很有意思,说不定以后会用到。
 用y个自然数ai的和表示自然数x,其中所有ai最小值最大的情况,如果x/y小数部分小于0.5,那么为[x/y]-[(y+1)/2]+1,否则为[x/y]-[y/2]+1,证明的话用反证法,应该不困难。mark一下。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值