0106 - HDU1107 - 武林

题目

0106 - HDU1107 - 武林
难度级别:D; 运行时间限制:1000ms; 运行空间限制:51200KB; 代码长度限制:2000000B
试题描述
试题描述较长,在后文中会逐段分析。
输入示例
2
1
S 1 2 20 20 20
W 2 1 20 20 20
0
2
S 1 2 20 20 20
W 2 1 20 20 20
E 12 12 20 20 100
0
输出示例
1 20
1 20
0 0
***
1 14
1 14
1 100
***

题解

思路

超级模拟题,对付这种题目,只有“题目说什么做什么”。但是理解题意也是不容易的(理科男出的题目何谈可读性(误) ),接下来博主带大家一同分析。

在一个有12行12列的方形的武林世界里,少林、武当和峨嵋三派的弟子们在为独霸武林而互相厮杀。武林世界的第一行的一列格子的坐标是(1, 1),第一行第二列坐标是(1, 2)……右下角的坐标为(12, 12)。

这段话先定义了“武林世界”,显然用二维数组存储即可,然后介绍了坐标原点和正方向,这个和C++的数组下标是一样的,顺着用就可以了。

少林派弟子总是在同一列回不停地行走。先往下走,走到头不能再走时就往上走,再到头则又往下走……比如,(1, 1) -> (2, 1) -> (3, 1)。
武当派弟子总是在同一行来回不停地行走。先往右走,走到头不能再走时就往左走,再到头则又往右走……比如,(2, 1) -> (2, 2) -> (2, 3)。
峨嵋派弟子总是在右下-左上方向来回不停走,先往右下方走,走到头不能再走时就往左上方走,再到头则又往右下方走……比如,(1, 1) -> (2, 2) -> (3, 3)。峨嵋弟子如果位于(1,12)或(12,1),那当然只能永远不动。
每次走动,每个弟子必须,而且只能移动一个格子。

我们得知了三种门派的弟子的行走方式,但是弟子是来回走的(类似于dvd待机动画),也就是说我们要整理一去一回 Δ x \Delta x Δx Δ y \Delta y Δy

Δ x \Delta x Δx-去 Δ y \Delta y Δy-去 Δ x \Delta x Δx-回 Δ y \Delta y Δy-回
少林派10-10
武当派010-1
峨眉派11-1-1

特别的是,如果峨眉派弟子在 ( 1 , 12 ) (1,12) (1,12) ( 12 , 1 ) (12,1) (12,1) 则会卡住不动,需要特判。

转化成代码就是这样,其中有一个d[2]那一行是为了峨眉派特判准备的。

const int dx[3][3]={{1,0,1},{-1,0,-1},{0,0,0}},dy[3][3]={{0,1,1},{0,-1,-1},{0,0,0}};

每名弟子有内力、武艺、和生命力三种属性。这三种属性的取值范围都是大于等于0,小于等于100。

给每个弟子增加了各种属性,对应的我们选用结构体存储。

struct pep
{
	char mp;//存储门派的字符
	int wy,nl,r,c,f,hp;//武艺,内里,当前行,当前列,行走方向(去/回),生命力
	bool isAlive;//是否存活,等价于(dz[i].hp>0)
}dz[maxn];

当有两名不同门派的弟子进入同一个格子时,一定会发生一次战斗,而且也只有在这种情况下,才会发生战斗。(同派弟子之间当然不会自相残杀;一个格子里三派弟子都有时,大家都会因为害怕别人渔翁得利而不敢出手;而多名同门派弟子也不会联手对付敌人,因为这有悖于武林中崇尚的单打独斗精神,会被人耻笑)

介绍了战斗开始条件,这段话必须要特别小心,很可能自己在某个判断的时候没看清题就错了。

分析一下:

1:弟子们只会单打独斗,不能联合战斗。(要不然这题更难了)

2:当且仅当格子里有两派弟子的时候,会发起战斗。

写了一个判断是否开始战斗的函数

inline bool isAbleToAttack(int t1,int t2)//常用的函数可以写成内联的形式
{
	int r1=dz[t1].r,c1=dz[t1].c,r2=dz[t2].r,c2=dz[t2].c,m1=getMp(t1),m2=getMp(t2);//获得弟子1、2的坐标和门派
	//坐标不同或者弟子未存活或同门派不能开启战斗
	if(r1!=r2 || c1!=c2 || !dz[t1].isAlive || !dz[t2].isAlive || m1==m2)return false;
	//多门派弟子不会联手对付敌人
	if(book[m1][r1][c1]>1 || book[m2][r1][c1]>1)return false;
	//若三派弟子全都在,不能开启战斗
	return !(book[0][r1][c1] && book[1][r1][c1] && book[2][r1][c1]);
}

一次战斗的结果将可能导致参战双方生命力发生变化,计算方法为:
战后生命力 = 战前生命力 - 对方攻击力
而不同门派的弟子攻击力计算方法不同:
少林派攻击力 = (0.5 * 内力 + 0.5 * 武艺) * (战前生命力 + 10) / 100
武当派攻击力 = (0.8 * 内力 + 0.2 * 武艺) * (战前生命力 + 10) / 100
峨嵋派攻击力 = (0.2 * 内力 + 0.8 * 武艺) * (战前生命力 + 10) / 100
对攻击力的计算过程为浮点运算,最终结果去掉小数点后部分取整,使得攻击力总是整数。
一次战斗结束后,生命力变为小于或等于0的弟子,被视为“战死”,会从武林中消失。
两名不同门派的弟子相遇时,只发生一次战斗。

给出了计算攻击力的公式,不同门派的弟子还有不同的权值,按照题意计算即可。不过这里需要注意何时取整,何时采用浮点计算。

const double buff[3][2]={{0.5,0.5},{0.8,0.2},{0.2,0.8}};
// ........
int getAttack(int x)
{
	int mp=getMp(x),nl=dz[x].nl,wy=dz[x].wy;//获得门派,内力和武艺
	double hp=dz[x].hp;
	return int((buff[mp][0]*nl+buff[mp][1]*wy)*(hp+10)/100.0);//进行计算
}

初始状态下,不存在生命值小于或等于0的弟子,而且一个格子里有可能同时有多个弟子。
一系列战斗从初始状态就可能爆发,全部战斗结束后,仍然活着的弟子才开始一齐走到下一个格子。总之,不停地战斗-行走-战斗-行走……所有弟子都需等战斗结束后,才一齐走到下一个格子。

有可能开场直接把两派弟子传送到一个格子立刻开始厮杀,因此我们在每一步的循环当中,应当先进行“战斗”的计算,再进行“行走”的计算。

接下来给出整体代码,博主电脑亲测比其他博主写的要慢一点…

只是为了帮助打模拟打了一下午,死活找不到bug的读者去理解

#include<iostream>//HDOJ貌似不支持万能头文件
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
const double buff[3][2]={{0.5,0.5},{0.8,0.2},{0.2,0.8}};//一些常量
const int maxn=1e4,dx[3][3]={{1,0,1},{-1,0,-1},{0,0,0}},dy[3][3]={{0,1,1},{0,-1,-1},{0,0,0}};
int tot,ans[3][2];
int book[3][15][15];//地图,分三个门派存储,区分的更清楚
struct pep
{
	char mp;
	int wy,nl,r,c,f,hp;
	bool fought,isAlive;
}dz[maxn];
int getMp(int x)
{
	char mp=dz[x].mp;//把门派从字符转换到整数型
	if(mp=='S')return 0;//这里的数字与buff,dx,dy数组相对应
	if(mp=='W')return 1;//博主写得比较繁琐了,可以在读入的时候之间转换
	if(mp=='E')return 2;
}
inline bool isAbleToAttack(int t1,int t2)
{
	int r1=dz[t1].r,c1=dz[t1].c,r2=dz[t2].r,c2=dz[t2].c,m1=getMp(t1),m2=getMp(t2);
	if(r1!=r2 || c1!=c2 || !dz[t1].isAlive || !dz[t2].isAlive || m1==m2)return false;
	if(book[m1][r1][c1]>1 || book[m2][r1][c1]>1)return false;
	return !(book[0][r1][c1]&&book[1][r1][c1]&&book[2][r1][c1]);
}
int getAttack(int x)
{
	int mp=getMp(x),nl=dz[x].nl,wy=dz[x].wy;
	double hp=dz[x].hp;
	return int((buff[mp][0]*nl+buff[mp][1]*wy)*(hp+10)/100.0);
}
void Update(int x)
{
	int f=dz[x].f,r=dz[x].r,c=dz[x].c,m=getMp(x);
	switch(m)
	{
		case 0:
			if(r==1)f=0;//触碰到边界,就更改方向
			if(r==12)f=1;
			break;
		case 1:
			if(c==1)f=0;
			if(c==12)f=1;
			break;
		case 2://注意下面三个if的顺序,如果错顺序会导致f被覆盖
			if(r==1||c==1)f=0;
			if(r==12||c==12)f=1;
			if((r==1&&c==12)||(r==12&&c==1))f=2;
	}
	if(dz[x].isAlive)//只有存活的弟子还能够走动
	{
		book[m][r][c]--;//原位置人数减一
		dz[x].r+=dx[f][m];
		dz[x].c+=dy[f][m];
		dz[x].f=f;
		book[m][dz[x].r][dz[x].c]++;//新位置人数加一
	}
	
}
void init()
{
	memset(ans,0,sizeof(ans));//由于有多组数据,需要将用到的数组全部清零
	memset(dz,0,sizeof(dz));
	memset(book,0,sizeof(book));
	tot=0;
}
int main()
{
	int T;
	scanf("%d",&T);
	while(T--)
	{
		init();
		int tstep;
		char ch[5];
		scanf("%d",&tstep);
		while(~scanf("%s",&ch)&&ch[0]!='0')//输入一个字符串,使用%c会出错,或者全部使用cin读入
		{
			dz[++tot].mp=ch[0];
			scanf("%d %d %d %d %d",&dz[tot].r,&dz[tot].c,&dz[tot].nl,&dz[tot].wy,&dz[tot].hp);
			dz[tot].isAlive=1;//赋予灵魂
			book[getMp(tot)][dz[tot].r][dz[tot].c]++;//人数加一
		}
		for(int k=1;k<=tstep;k++)//循环步数
		{
			for(int i=1;i<tot;i++)//两两弟子之间比较
			{
				for(int j=i+1;j<=tot;j++)
				{
					if(isAbleToAttack(i,j))//可以战斗
					{
						int atki=getAttack(i),atkj=getAttack(j);
						dz[i].hp-=atkj;
						dz[j].hp-=atki;
						if(dz[i].hp<=0)book[getMp(i)][dz[i].r][dz[i].c]--,dz[i].isAlive=0;//有人hp为0了就宣判死亡
						if(dz[j].hp<=0)book[getMp(j)][dz[i].r][dz[i].c]--,dz[j].isAlive=0;
					}
				}
			}
			for(int i=1;i<=tot;i++)Update(i);//更新弟子的位置
		}
		for(int i=1;i<=tot;i++)
		{
			if(dz[i].isAlive)//统计活着的弟子人数和血量总和
			{
				ans[getMp(i)][0]++;
				ans[getMp(i)][1]+=dz[i].hp;
			}
		}
		for(int i=0;i<3;i++) printf("%d %d\n",ans[i][0],ans[i][1]);
		printf("***\n");
	}
	return 0;
}

写在后面

打了一下午的大模拟终于调好了,题目的给的测试数据很水,找不到问题的时候,可以从网上找别人的标称,自己造几套数据进行对拍。

附上使用 洛谷CYARON 的数据生成脚本

#!/usr/bin/env python

from cyaron import *

for i in range(1, 11):
    test_data = IO(file_prefix="fight", data_id=i)
    n=randint(20,30)
    a=['S','W','E']
    test_data.input_writeln(n)
    for j in range(0,n):
        m=randint(20,200)
        test_data.input_writeln(randint(20,200)) #这里范围开大在博主电脑上跑会比较慢,不过HDOJ的时限是5000MS就没问题了
        for k in range(0,m):
            test_data.input_writeln(a[randint(0,2)],randint(1,12),randint(1,12),randint(1,100),randint(1,100),randint(1,100))
        test_data.input_writeln('0')
    test_data.output_gen("hdu1107-csdn.exe")

test_data=1

(其实是学校OJ没有数据,只能自测)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值