杀蚂蚁
Time limit: 3000 ms
Memory limit: 256 MB
Standard I/O
Content
最近,佳佳迷上了一款好玩
的小游戏:antbuster。
游戏规则非常简单:在一张地图上,左上角是蚂蚁窝,右下角是蛋糕,蚂蚁会源源不断地从窝里爬出来,试图把蛋糕搬回蚂蚁窝。而你的任务,就是用原始资金以及杀蚂蚁获得的奖金造防御塔,杀掉这些试图跟你抢蛋糕的蚂蚁~
下附一张游戏截图:
为了拿到尽可能高的分数,佳佳设计了很多种造塔的方案,但在尝试了其中的一小部分后,佳佳发现,这个游戏实在是太费时间了。为了节省时间,佳佳决定写个程序,对于每一种方案,模拟游戏进程,根据效果来判断方案的优劣。
根据自己在游戏中积累的一些经验,以及上网搜到的一些参数,佳佳猜了蚂蚁爬行的算法,并且假设游戏中的蚂蚁也是按这个规则选择路线:
- 每一秒钟开始的时候,蚂蚁都在平面中的某个整点上。如果蚂蚁没有扛着蛋糕,它会在该点留下2单位的信息素,否则它会留下5单位的信息素。然后蚂蚁会在正北、正南、正东、正西四个方向中选择一个爬过去。
- 选择方向的规则是:首先,爬完一个单位长度后到达的那个点上,不能有其他蚂蚁或是防御塔,并且那个点不能是蚂蚁上一秒所在的点(除非上一个时刻蚂蚁就被卡住,且这个时刻它仍无法动),当然,蚂蚁也不会爬出地图的边界(我们定义这些点为不可达点)。如果此时有多个选择,蚂蚁会选择信息素最多的那个点爬过去。
- 如果此时仍有多种选择,蚂蚁先面向正东,如果正东不是可选择的某个方向,它会顺时针转90°,再次判断,如果还不是,再转90°…直到找到可以去的方向。
- 如果将每只蚂蚁在洞口出现的时间作为它的活动时间的第1秒,那么每当这只蚂蚁的活动时间秒数为5的倍数的时候,它先按规则1~3确定一个方向,面对该方向后逆时针转90°,若它沿当前方向会走到一个不可达点,它会不停地每次逆时针转90°,直到它面对着一个可达的点,这样定下的方向才是蚂蚁最终要爬去的方向。
- 如果蚂蚁的四周都是不可达点,那么蚂蚁在这一秒内会选择停留在当前点。下一秒判断移动方向时,它上一秒所在点为其当前停留的点。
- 你可以认为蚂蚁在选定方向后,瞬间移动到它的目标点,这一秒钟剩下的时间里,它就停留在目标点。
- 蚂蚁按出生的顺序移动,出生得比较早的蚂蚁先移动。
然后,是一些有关地图的信息:
- 每一秒,地图所有点上的信息素会损失1单位,如果那个点上有信息素的话。
- 地图上某些地方是炮台。炮台的坐标在输入中给出。
- 地图的长、宽在输入中给出,对于n * m的地图,它的左上角坐标为(0,0),右下角坐标为(n,m)。蚂蚁洞的位置为(0,0),蛋糕的位置为(n,m)。
- 你可以把蚂蚁看做一个直径为1单位的圆,圆心位于蚂蚁所在的整点。
- 游戏开始时,地图上没有蚂蚁,每个点上的信息素含量均为0。
一些有关炮塔的信息:
- 炮塔被放置在地图上的整点处。
- 为了简单一些,我们认为这些炮塔都是激光塔。激光塔的射速是1秒/次,它的攻击伤害为d/次,攻击范围为r。你可以认为每秒蚂蚁移动完毕后,塔才开始攻击。并且,只有当代表蚂蚁的圆的圆心与塔的直线距离不超过r时,塔才算打得到那只蚂蚁。
- 如果一只蚂蚁扛着蛋糕,那么它会成为target,也就是说,任何打得到它的塔的炮口都会对准它。如果蛋糕好好地呆在原位,那么每个塔都会挑离它最近的蚂蚁进行攻击,如果有多只蚂蚁,它会选出生较早的一只。
- 激光塔有个比较奇怪的特性:它在选定了打击目标后,只要目标在其射程内,塔到目标蚂蚁圆心的连线上的所有蚂蚁(这里“被打到”的判定变成了表示激光的线段与表示蚂蚁的圆有公共点)都会被打到并损d格血,但激光不会穿透它的打击目标打到后面的蚂蚁。
- 尽管在真实游戏中,塔是可以升级的,但在这里我们认为塔的布局和等级就此定了下来,不再变动。
再介绍一下蚂蚁窝:
- 如果地图上的蚂蚁不足6只,并且洞口没有蚂蚁,那么窝中每秒会爬出一只蚂蚁,直到地图上的蚂蚁数为6只。
- 刚出生的蚂蚁站在洞口。
- 每只蚂蚁有一个级别,级别决定了蚂蚁的血量,级别为k的蚂蚁的血量为int(4*1.1^k)(int(x)表示对x取下整)。每被塔打一次,蚂蚁的血减少d。注意,血量为0的蚂蚁仍能精力充沛地四处乱爬,只有一只蚂蚁的血被打成负数时,它才算挂了。
- 蚂蚁的级别是这样算的:前6只出生的蚂蚁是1级,第7~12只是2级,依此类推。
最后给出关于蛋糕的介绍:
- 简单起见,你可以认为此时只剩最后一块蛋糕了。如果有蚂蚁走到蛋糕的位置,并且此时蛋糕没有被扛走,那么这只蚂蚁就扛上了蛋糕。蚂蚁被打死后蛋糕归位。
- 如果一只扛着蛋糕的蚂蚁走到蚂蚁窝的位置,我们就认为蚂蚁成功抢到了蛋糕,游戏结束。
- 蚂蚁扛上蛋糕时,血量会增加int(该蚂蚁出生时血量 / 2),但不会超过上限。
整理一下1秒钟内发生的事件:
- 1秒的最初,如果地图上蚂蚁数不足6,一只蚂蚁就会在洞口出生。
- 接着,蚂蚁们在自己所在点留下一些信息素后,考虑移动。先出生的蚂蚁先移动。
- 移动完毕后,如果有蚂蚁在蛋糕的位置上并且蛋糕没被拿走,它把蛋糕扛上,血量增加,并在这时被所有塔设成target。
- 然后所有塔同时开始攻击。如果攻击结束后那只扛着蛋糕的蚂蚁挂了,蛋糕瞬间归位。
- 攻击结束后,如果发现扛蛋糕的蚂蚁没死并在窝的位置,就认为蚂蚁抢到了蛋糕。游戏也在此时结束。
- 最后,地图上所有点的信息素损失1单位。所有蚂蚁的年龄加1。
- 漫长的1秒到此结束。
Input Description
输入的第一行是2个用空格隔开的整数,n、m,分别表示了地图的长和宽。
第二行是3个用空格隔开的整数,s、d、r,依次表示炮塔的个数、单次攻击伤害以及攻击范围。
接下来s行,每行是2个用空格隔开的整数x、y,描述了一个炮塔的位置。当然,蚂蚁窝的洞口以及蛋糕所在的位置上一定没有炮塔。
最后一行是一个正整数t,表示我们模拟游戏的前t秒钟。
Output Description
如果在第t秒或之前蚂蚁抢到了蛋糕,输出一行“Game over after x seconds”,其中x为游戏结束的时间,否则输出“The game is going on”。
如果游戏在t秒或之前结束,输出游戏结束时所有蚂蚁的信息,否则输出t秒后所有蚂蚁的信息。格式如下:
第一行是1个整数s,表示此时活着的蚂蚁的总数。
接下来s行,每行5个整数,依次表示一只蚂蚁的年龄(单位为秒)、等级、当前血量,以及在地图上的位置(a,b)。输出按蚂蚁的年龄递减排序。
Sample
INPUT
3 5
1 1 2
2 2
5
OUTPUT
The game is going on
5
5 1 3 1 4
4 1 3 0 4
3 1 3 0 3
2 1 3 0 2
1 1 4 0 1
Hint
样例说明:
3*5的地图,有1个单次伤害为1、攻击范围为2的激光炮塔,它的位置为(2,2),模拟游戏的前5秒。5秒内有5只蚂蚁出生,都是向东爬行,其中第1
4只在路过(0,2)点时被激光塔伤了1格血。在第5秒的时候,最早出生的蚂蚁按移动规则13本来该向东移动,但由于规则4的作用,它在发现向北和向西移动都会到达不可达点后,最终选择了向南移动。
数据说明:
100%的数据满足1 ≤ n,m ≤ 8,s ≤ 20,t ≤ 200,000
Code
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#define CANNON 1
#define ANT 2
//#define DEBUG 0
struct antdata{
int age;
int id;//蚂蚁的id,从1开始不重复,往上递增,为0表示不存在
int rank;//等级
int health;//血量
int x,y;
int lastx,lasty;
int cake;//0或1,是否有蛋糕
}ant[6]={};
struct cannondata{
int x,y;
};
int time=0;
int cake=-1;//记录蛋糕在哪只蚂蚁身上0-5
int antid=0;//记录蚂蚁出生的顺序
int antnum=0;//记录当前场上的蚂蚁数量
int **map,**info;//记录地图上的蚂蚁/炮台,信息素
int n,m;
int cnum,churt,cdistance;//炮台cannon数,炮台伤害,炮台攻击范围
struct cannondata *cannon;
int direction[4][2]={{0,1},{1,0},{0,-1},{-1,0}};//右下左上(东南西北,顺时针)
int directionavailable[4]={};//记录可前进的方向
void initialize(){
int i,j;
for(i=0;i<=n;i++) for(j=0;j<=m;j++) {info[i][j]=0; map[i][j]=0;}
}
void MakeAnt(){//不满6只则生成蚂蚁
int i;
if(antnum<6 && map[0][0]==0){
//for(i=0;i<6;i++)
i=antnum;
//if(ant[i].id==0){
antid++;
ant[i].id=antid;
ant[i].x=0;
ant[i].y=0;
ant[i].lastx=0;
ant[i].lasty=0;
ant[i].age=0;
ant[i].rank=(antid-1)/6+1;
ant[i].health=floor(4*pow(1.1,ant[i].rank));
ant[i].cake=0;
//}
antnum++;
map[0][0]=ANT+ant[i].id;
}
}
void DeleteAnt(int i){//i为0-5
int j;
for(j=i+1;j<antnum;j++){//后面的前移
ant[j-1]=ant[j];
}
antnum--;
for(j=antnum;j<6;j++){//置零表示不存在
ant[j].id=0;
}
}
void GiveInfo(){//增加信息素
int i;
for(i=0;i<6;i++)
if(ant[i].id!=0)
if(ant[i].cake)
info[ant[i].x][ant[i].y]+=5;
else
info[ant[i].x][ant[i].y]+=2;
}
void LostInfo(){//失去信息素
int i,j;
for(i=0;i<=n;i++) for(j=0;j<=m;j++) if(info[i][j]) info[i][j]--;
}
void PutAnt(int i,int x,int y){//放置蚂蚁并在地图上标记
ant[i].lastx=ant[i].x;
ant[i].lasty=ant[i].y;
map[ant[i].x][ant[i].y]=0;
#ifdef DEBUG
printf("AntID:%d from (%d,%d) to (%d,%d), it's time/Rank/Health is %d/%d/%d\n",ant[i].id,ant[i].x,ant[i].y,x,y,ant[i].age+1,ant[i].rank,ant[i].health);
#endif
ant[i].x=x;
ant[i].y=y;
map[x][y]=ANT+ant[i].id;
}
int SelectDirection(int i){//返回一个方向的序号,不能移动返回-1
int j,tx,ty,anum=0,a,amaxnum=0;
memset(directionavailable,0,sizeof(directionavailable));
for(j=0;j<4;j++){
tx=ant[i].x+direction[j][0];
ty=ant[i].y+direction[j][1];
if(tx==ant[i].lastx && ty==ant[i].lasty) continue;
if(tx<0 || ty<0 || tx>n || ty>m) continue;
if(map[tx][ty]) continue;
directionavailable[j]=1;//置为可达
a=j;
anum++;//可达的数量加1
}
if(anum==0) return -1;
if(anum==1) return a;
//此时有多个可达点
int max=-1;
for(j=0;j<4;j++){
if(directionavailable[j]){
tx=ant[i].x+direction[j][0];
ty=ant[i].y+direction[j][1];
directionavailable[j]=info[tx][ty]+1;//将可达标志改为该方向前进一格的信息素+1,+1为了将信息素为0的与不可达点区分
if(directionavailable[j]>max) max=directionavailable[j];//更新最大值
}
}
for(j=0;j<4;j++){//该循环将信息素不是最大值的置零 (后来发现题意不是如此,不是最大值的仍是可达点,不该置零,这里做特殊标记-1)
if(directionavailable[j]){//是可达点
if(directionavailable[j]<max){//但不是信息素最大的
directionavailable[j]=-1;
//anum--;
}
else{//是最大值
a=j;// 记下最大值的下标,如果有多个,a将是最后一个最大值
amaxnum++;//最大值方向的数量
}
}
}
if(amaxnum!=1){//有多个最大值
a=0;//东
while(directionavailable[a]<=0){//该方向不是最大值的方向(-1),或不可达(0)
a++;//顺时针转90度
}
}
//在多个可达点时才考虑年龄为5的倍数的情况,因为只有1个方向可达时,逆时针多次后最终仍是该方向
if((ant[i].age+1)%5==0){
do{
a--;//逆时针转90度
if(a<0) a+=4;
}while(directionavailable[a]==0);//该方向不可达
}
return a;
}
void Move(){
int i,tx,ty,d;
for(i=0;i<antnum;i++){
//if(ant[i].id==0) break;//后面都是空的
d=SelectDirection(i);
if(d==-1) PutAnt(i,ant[i].x,ant[i].y);
else {
tx=ant[i].x+direction[d][0];
ty=ant[i].y+direction[d][1];
PutAnt(i,tx,ty);
}
if(ant[i].x==n && ant[i].y==m && cake==-1){//到达蛋糕位置
ant[i].cake=1;
cake=i;
ant[i].health+=floor(floor(4*pow(1.1,ant[i].rank))/2);
if(ant[i].health>(int)floor(4*pow(1.1,ant[i].rank)))
ant[i].health=(int)floor(4*pow(1.1,ant[i].rank));
}
}
}
int GetDistance2(int x1,int y1,int x2,int y2){//为了避免浮点数运算,这里计算距离的平方
return (x2-x1)*(x2-x1)+(y2-y1)*(y2-y1);
}
int VectorProduct2(int x1,int y1,int x2,int y2){//两向量矢积的平方
return (x1*y2-x2*y1)*(x1*y2-x2*y1);
}
int Mult(int x1,int y1,int x2,int y2){//两向量数量积
return x1*x2+y1*y2;
}
int IsInAttackArea(int x1,int y1,int x2,int y2,int x3,int y3){
//判断以3点为圆心,半径为0.5的圆和1,2点连成的线段有无公共点(后来才注意蚂蚁的直径是1,不是半径...)
int tmp,tmp2;
tmp2=(x2-x1)*(x2-x1)+(y2-y1)*(y2-y1);
tmp=Mult(x3-x1,y3-y1,x2-x1,y2-y1);
if(tmp<0 || tmp>tmp2){//printf("----------s1---------\n");
return 0;
}
tmp=VectorProduct2(x3-x1,y3-y1,x2-x1,y2-y1);
if(4*tmp>tmp2) {//printf("----------s2---------\n");
return 0;
}
return 1;
}
void Attack(){//所有炮塔是一起攻击的,攻击结束后才判断蚂蚁生死
int i,j;
for(j=0;j<cnum;j++){
int imin=-1,target=-1;
int d2min=9999999,d2;
if(cake!=-1 && GetDistance2(ant[cake].x,ant[cake].y,cannon[j].x,cannon[j].y)<=cdistance*cdistance){
target=cake;
}
else{
for(i=0;i<antnum;i++){
//if(ant[i].id==0) break;//后面都是空的
d2=GetDistance2(ant[i].x,ant[i].y,cannon[j].x,cannon[j].y);
if(d2<d2min) {d2min=d2;imin=i;}
}
target=imin;
if(GetDistance2(ant[target].x,ant[target].y,cannon[j].x,cannon[j].y)>cdistance*cdistance) continue;//最近的都打不到
}
ant[target].health-=churt;
#ifdef DEBUG
printf("Targer:%d,TargetAntId:%d\n",target,ant[target].id);
#endif
for(i=0;i<antnum;i++){
if(i==target) continue;
if(IsInAttackArea(ant[target].x,ant[target].y,cannon[j].x,cannon[j].y,ant[i].x,ant[i].y)){
ant[i].health-=churt;
#ifdef DEBUG
printf("!!!!!!!!!!!!!!!!!ExtraTargetAntId:%d!!!!!!!!!!!!!!!!!!!\n",ant[i].id);
//system("pause");
#endif
}
}
}
}
void Judge1(){
int i;
for(i=0;i<antnum;i++){
if(ant[i].health<0){
#ifdef DEBUG
printf("AntID:%d died.\n",ant[i].id);
#endif
map[ant[i].x][ant[i].y]=0;
if(ant[i].cake) cake=-1;//蛋糕归位
else if(cake!=-1) if(cake>i) cake--;
DeleteAnt(i);
i--;
}
}
}
int Judge2(){
if(cake!=-1) if(ant[cake].x==0 && ant[cake].y==0) return 1;
return 0;
}
void AgeAdd(){
int i;
for(i=0;i<antnum;i++) ant[i].age++;
}
//-------------调试-----------
int GetHealthById(int id){
int i;
for(i=0;i<antnum;i++) if(ant[i].id==id) return ant[i].health;
return -1;
}
void PFSTATE(){
int i,j;
for(i=0;i<=n;i++){
for(j=0;j<=m;j++){
printf("[%3d]",info[i][j]);
if(map[i][j]==CANNON) printf(" CANNON ");
else if(map[i][j]>ANT) printf("%4d(%2d)",map[i][j]-ANT,GetHealthById(map[i][j]-ANT));
else printf("|------|");
}
printf("\n");
}
printf("Cake:%d,CakeAntId:%d,ant[cake].cake:%d\n",cake,ant[cake].id,ant[cake].cake);
printf("=====================time: %d=====================\n",time);
}
//---------------------------
int Clock(){//返回1表示结束
time++;
MakeAnt();
GiveInfo();
Move();
Attack();
Judge1();
if(Judge2()) return 1;
AgeAdd();
LostInfo();
#ifdef DEBUG
PFSTATE();
#endif
return 0;
}
int main(){
int i,tx,ty,runtime,isend=0;
scanf("%d%d",&n,&m);
map=(int **)malloc(sizeof(int *)*(n+1));
info=(int **)malloc(sizeof(int *)*(n+1));
for(i=0;i<=n;i++){
map[i]=(int *)malloc(sizeof(int)*(m+1));
info[i]=(int *)malloc(sizeof(int *)*(m+1));
}
initialize();
scanf("%d%d%d",&cnum,&churt,&cdistance);
cannon=(struct cannondata *)malloc(cnum*sizeof(struct cannondata));
for(i=0;i<cnum;i++){
scanf("%d%d",&tx,&ty);
map[tx][ty]=CANNON;
cannon[i].x=tx;
cannon[i].y=ty;
}
scanf("%d",&runtime);
for(i=0;i<runtime;i++){
if(Clock()){
printf("Game over after %d seconds\n",time);
isend=1;
break;
}
}
if(!isend) printf("The game is going on\n");
printf("%d\n",antnum);
for(i=0;i<antnum;i++){
printf("%d %d %d %d %d\n",ant[i].age,ant[i].rank,ant[i].health,ant[i].x,ant[i].y);
}
return 0;
}