题目
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-回 | |
---|---|---|---|---|
少林派 | 1 | 0 | -1 | 0 |
武当派 | 0 | 1 | 0 | -1 |
峨眉派 | 1 | 1 | -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没有数据,只能自测)