BZOJ1033【ZJOI2007】杀蚂蚁 <模拟>

Problem

【ZJOI2008】杀蚂蚁

Description

最近,佳佳迷上了一款好玩的小游戏:antbuster\mathrm{antbuster}
游戏规则非常简单:在一张地图上,左上角是蚂蚁窝,右下角是蛋糕,蚂蚁会源源不断地从窝里爬出来,试图把蛋糕搬回蚂蚁窝。而你的任务,就是用原始资金以及杀蚂蚁获得的奖金造防御塔,杀掉这些试图跟你抢蛋糕的蚂蚁~
下附一张游戏截图:

![](http://www.lydsy.com/JudgeOnline/images/1033/1.jpg)

为了拿到尽可能高的分数,佳佳设计了很多种造塔的方案,但在尝试了其中的一小部分后,佳佳发现,这个游戏实在是太费时间了。为了节省时间,佳佳决定写个程序,对于每一种方案,模拟游戏进程,根据效果来判断方案的优劣。
根据自己在游戏中积累的一些经验,以及上网搜到的一些参数,佳佳猜了蚂蚁爬行的算法,并且假设游戏中的蚂蚁也是按这个规则选择路线:
1. 每一秒钟开始的时候,蚂蚁都在平面中的某个整点上。如果蚂蚁没有扛着蛋糕,它会在该点留下 2 单位的信息素,否则它会留下 5 单位的信息素。然后蚂蚁会在正北、正南、正东、正西四个方向中选择一个爬过去。
2. 选择方向的规则是:首先,爬完一个单位长度后到达的那个点上,不能有其他蚂蚁或是防御塔,并且那个点不能是蚂蚁上一秒所在的点(除非上一个时刻蚂蚁就被卡住,且这个时刻它仍无法动),当然,蚂蚁也不会爬出地图的边界(我们定义这些点为不可达点)。如果此时有多个选择,蚂蚁会选择信息素最多的那个点爬过去。
3. 如果此时仍有多种选择,蚂蚁先面向正东,如果正东不是可选择的某个方向,它会顺时针转90°,再次判断,如果还不是,再转 90°…直到找到可以去的方向。
4. 如果将每只蚂蚁在洞口出现的时间作为它的活动时间的第 1 秒,那么每当这只蚂蚁的活动时间秒数为 5 的倍数的时候,它先按规则 1~3 确定一个方向,面对该方向后逆时针转 90°,若它沿当前方向会走到一个不可达点,它会不停地每次逆时针转 90°,直到它面对着一个可达的点,这样定下的方向才是蚂蚁最终要爬去的方向。
5. 如果蚂蚁的四周都是不可达点,那么蚂蚁在这一秒内会选择停留在当前点。下一秒判断移动方向时,它上一秒所在点为其当前停留的点。
6. 你可以认为蚂蚁在选定方向后,瞬间移动到它的目标点,这一秒钟剩下的时间里,它就停留在目标点。
7. 蚂蚁按出生的顺序移动,出生得比较早的蚂蚁先移动。
然后,是一些有关地图的信息:
1. 每一秒,地图所有点上的信息素会损失 1 单位,如果那个点上有信息素的话。
2. 地图上某些地方是炮台。炮台的坐标在输入中给出。
3. 地图的长、宽在输入中给出,对于nm的地图,它的左上角坐标为(0,0),右下角坐标为(n,m)。蚂蚁洞的位置为(0,0),蛋糕的位置为(n,m)。
4. 你可以把蚂蚁看做一个直径为 1 单位的圆,圆心位于蚂蚁所在的整点。
5. 游戏开始时,地图上没有蚂蚁,每个点上的信息素含量均为 0。
一些有关炮塔的信息:
1. 炮塔被放置在地图上的整点处。
2. 为了简单一些,我们认为这些炮塔都是激光塔。激光塔的射速是 1 秒/次,它的攻击伤害为 d/次,攻击范围为 r。你可以认为每秒蚂蚁移动完毕后,塔才开始攻击。并且,只有当代表蚂蚁的圆的圆心与塔的直线距离不超过 r 时,塔才算打得到那只蚂蚁。
3. 如果一只蚂蚁扛着蛋糕,那么它会成为 target,也就是说,任何打得到它的塔的炮口都会对准它。如果蛋糕好好地呆在原位,那么每个塔都会挑离它最近的蚂蚁进行攻击,如果有多只蚂蚁,它会选出生较早的一只。
4. 激光塔有个比较奇怪的特性:它在选定了打击目标后,只要目标在其射程内,塔到目标蚂蚁圆心的连线上的所有蚂蚁(这里“被打到”的判定变成了表示激光的线段与表示蚂蚁的圆有公共点)都会被打到并损 d 格血,但激光不会穿透它的打击目标打到后面的蚂蚁。
5. 尽管在真实游戏中,塔是可以升级的,但在这里我们认为塔的布局和等级就此定了下来,不再变动。
再介绍一下蚂蚁窝:
1. 如果地图上的蚂蚁不足 6 只,并且洞口没有蚂蚁,那么窝中每秒会爬出一只蚂蚁,直到地图上的蚂蚁数为 6 只。
2. 刚出生的蚂蚁站在洞口。3. 每只蚂蚁有一个级别,级别决定了蚂蚁的血量,级别为 k 的蚂蚁的血量为[4
1.1^k]。每被塔打一次,蚂蚁的血减少 d。注意,血量为 0 的蚂蚁仍能精力充沛地四处乱爬,只有一只蚂蚁的血被打成负数时,它才算挂了。
3. 蚂蚁的级别是这样算的:前 6 只出生的蚂蚁是 1 级,第 7~12 只是 2 级,依此类推。
最后给出关于蛋糕的介绍:
1. 简单起见,你可以认为此时只剩最后一块蛋糕了。如果有蚂蚁走到蛋糕的位置,并且此时蛋糕没有被扛走,那么这只蚂蚁就扛上了蛋糕。蚂蚁被打死后蛋糕归位。
2. 如果一只扛着蛋糕的蚂蚁走到蚂蚁窝的位置,我们就认为蚂蚁成功抢到了蛋糕,游戏结束。
3. 蚂蚁扛上蛋糕时,血量会增加[该蚂蚁出生时血量/2],但不会超过上限。
整理一下 1 秒钟内发生的事件:
1. 1 秒的最初,如果地图上蚂蚁数不足 6,一只蚂蚁就会在洞口出生。
2. 接着,蚂蚁们在自己所在点留下一些信息素后,考虑移动。先出生的蚂蚁先移动。
3. 移动完毕后,如果有蚂蚁在蛋糕的位置上并且蛋糕没被拿走,它把蛋糕扛上,血量增加,并在这时被所有塔设成 target。
4. 然后所有塔同时开始攻击。如果攻击结束后那只扛着蛋糕的蚂蚁挂了,蛋糕瞬间归位。
5. 攻击结束后,如果发现扛蛋糕的蚂蚁没死并在窝的位置,就认为蚂蚁抢到了蛋糕。游戏也在此时结束。
6. 最后,地图上所有点的信息素损失 1 单位。所有蚂蚁的年龄加 1。
7. 漫长的 1 秒到此结束。

Input

输入的第一行是 22 个用空格隔开的整数, nnmm,分别表示了地图的长和宽。
第二行是 33 个用空格隔开的整数, ssddrr,依次表示炮塔的个数、单次攻击伤害以及攻击范围。接下来 ss 行,每行是 22 个用空格隔开的整数 xxyy,描述了一个炮塔的位置。当然,蚂蚁窝的洞口以及蛋糕所在的位置上一定没有炮塔。
最后一行是一个正整数 tt,表示我们模拟游戏的前 tt 秒钟。

Output

如果在第 tt 秒或之前蚂蚁抢到了蛋糕,输出一行“Game  over  after  x  seconds\mathrm{Game\;over\;after\;}x\mathrm{\;seconds}”,其中xx为游戏结束的时间,否则输出“The  game  is  going  on\mathrm{The\;game\;is\;going\;on}”。
如果游戏在 tt​ 秒或之前结束,输出游戏结束时所有蚂蚁的信息,否则输出 tt​ 秒后所有蚂蚁的信息。
格式如下:第一行是 11 个整数 ss,表示此时活着的蚂蚁的总数。接下来 ss 行,每行 55 个整数,依次表示一只蚂蚁的年龄(单位为秒)、等级、当前血量,以及在地图上的位置(a,b)(a,b)。输出按蚂蚁的年龄递减排序。

Sample Input

1
2
3
4
3 5
1 1 2
2 2
5

Sample Output

1
2
3
4
5
6
7
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×53\times 5的地图,有11个单次伤害为11、攻击范围为22的激光炮塔,它的位置为(2,2)(2,2),模拟游戏的前55秒。55秒内有55只蚂蚁出生,都是向东爬行,其中第141\sim 4只在路过(0,2)(0,2)点时被激光塔伤了11格血。在第55秒的时候,最早出生的蚂蚁按移动规则131\sim 3本来该向东移动,但由于规则44的作用,它在发现向北和向西移动都会到达不可达点后,最终选择了向南移动。
数据说明
100%100\%的数据满足1n,m81\le n,m\le 8s20s\le 20t2×105t\le 2\times 10^5

如果觉得此题面不好看,请戳这里:https://www.zybuluo.com/Jerusalem/note/221811
如果做得无聊了,可以玩玩:http://wanga.me/2794

标签:大模拟

Solution

这是那种“做一做,一个下午就没了”的恶心题。
我从上午十一点开做,足足做到晚上七点半才做完。
恶心~

大模拟,题面已经比较清楚了。
坑点如下:

  1. 蚂蚁既是一个点,也是一个直径为11的圆(注意,是直径!)
    即在判断一个炮台和一只蚂蚁的距离时,应取圆心与炮台距离,但在判断一只蚂蚁是否会被炮台顺带打到,即在打目标蚂蚁的路径上时,应判断激光直线是否与圆有交点。
  2. 新出生的蚂蚁刚从蚁巢里出来时,要标记(0,0)(0,0)为有物体,否则回来的蚂蚁会和它重在一起。
  3. 若当前蚂蚁出生的时间为tt,则其年龄为t1t-1
  4. 寻找移动方向时,应先按规则131\sim 3选择下一个位置。如果出生时间为55的倍数,则应该逆时针(注意,是逆时针)旋转,并且找到一个可行方向就走过去,不考虑信息素多少。
  5. 所有炮台是一起开炮,所以应该先把所有炮台的目标确定后,再统一扣蚂蚁的生命值。这样会出现蚂蚁生命在同一秒内被扣成负数还要继续扣的情况,这样就是正确的。

Extra Samples

贡献几组数据,可自测:

Sample #1

Input

1
2
3
4
5
6
7
4 4
4 10 5
1 1
2 2
1 2
2 1
1000

Output

1
2
3
4
5
6
7
8
Game over after 831 seconds
6
110 49 86 2 3
88 49 26 0 1
77 49 286 3 1
58 50 29 0 0
22 50 419 4 3
10 50 469 3 2
Sample #2

Input

1
2
3
4
6 4
1 10000 10
2 2
256

Output

1
2
The game is going on
0
Sample #3

Input

1
2
3
4 7
0 0 0
60200

Output

1
2
3
4
5
6
7
8
Game over after 60200 seconds
6
60199 1 4 0 0
60198 1 4 2 6
60197 1 4 4 7
60196 1 4 2 1
60195 1 4 3 5
60194 1 4 4 5
Sample #4

Input

1
2
3
4
5
6
7
8
9
2 5
6 1 3
0 1
1 1
1 3
1 4
2 3
2 4
200000

Output

1
2
3
4
5
6
7
8
The game is going on
6
15311 86 13525 1 5
12518 86 10203 0 5
9447 87 12495 0 4
6371 87 13461 0 3
3295 87 14586 0 2
219 87 15873 1 2

Code

BZOJ\mathrm{BZOJ}上过了,洛谷上死活WA\mathrm{WA}一个点,不知道为什么)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
#include <iostream>
#include <cstdio>
#include <vector>
#include <cmath>
#define EPS 1e-5
using namespace std;
typedef double dnt;
int n, m, s, d, r, cnt, num, tar = -1;
vector <int> living; int information[10][10];
bool lose = false, cake = true, is_taken[10][10];
int nxt[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
int calc_lim_blood(int k) {dnt ret = 4; while (k--) ret *= 1.1; return (int)floor(ret);}
struct Ant {
int x, y, id, px, py; bool has_cake;
int cur_blood, lim_blood, level, age;
void birth(int _id) {
living.push_back(id = _id), num++, has_cake = false;
x = y = age = 0, px = py = -1, level = id/6+1;
cur_blood = lim_blood = calc_lim_blood(level), is_taken[x][y] = true;
}
void dead() {
num--; int pos;
for (pos = 0; pos < (int)living.size(); pos++) if (living[pos] == id) break;
for (; pos < (int)living.size()-1; pos++) living[pos] = living[pos+1];
living.erase(living.end()-1);
if (has_cake) cake = true, tar = -1; is_taken[x][y] = false;
}
void recover() {cur_blood = min(cur_blood+(int)floor(lim_blood/2), lim_blood);}
void hurt() {cur_blood -= d;}
void left_information() {information[x][y] += has_cake ? 5 : 2;}
pair <int, int> choose_direction() {
int nx = -1, ny = -1, cur;
for (int i = 0; i < 4; i++) {
int tx = x+nxt[i][0], ty = y+nxt[i][1];
if (tx < 0 || tx > n || ty < 0 || ty > m) continue;
if (is_taken[tx][ty] || (tx == px && ty == py)) continue;
if (nx == -1 && ny == -1) nx = tx, ny = ty;
else if (information[tx][ty] > information[nx][ny]) nx = tx, ny = ty;
}
if ((age+1)%5 == 0) {
if (nx == x && ny == y+1) nx = ny = -1, cur = 0; if (nx == x+1 && ny == y) nx = ny = -1, cur = 1;
if (nx == x && ny == y-1) nx = ny = -1, cur = 2; if (nx == x-1 && ny == y) nx = ny = -1, cur = 3;
for (int i = cur-1; ~i; i--) {
int tx = x+nxt[i][0], ty = y+nxt[i][1];
if (tx < 0 || tx > n || ty < 0 || ty > m) continue;
if (is_taken[tx][ty] || (tx == px && ty == py)) continue;
if (nx == -1 && ny == -1) nx = tx, ny = ty;
}
for (int i = 3; i >= cur; i--) {
int tx = x+nxt[i][0], ty = y+nxt[i][1];
if (tx < 0 || tx > n || ty < 0 || ty > m) continue;
if (is_taken[tx][ty] || (tx == px && ty == py)) continue;
if (nx == -1 && ny == -1) nx = tx, ny = ty;
}
}
return make_pair(nx, ny);
}
void move() {
pair <int, int> np = choose_direction();
if (np.first == -1 && np.second == -1) np.first = x, np.second = y;
is_taken[x][y] = false, is_taken[np.first][np.second] = true;
px = x, py = y, x = np.first, y = np.second;
}
bool can_take_cake() {return x == n && y == m && cake;}
void take_cake() {if (can_take_cake()) recover(), cake = false, has_cake = true, tar = id;}
bool steal_cake() {return x == 0 && y == 0 && has_cake;}
} ant[2000000];
dnt dis(int x1, int y1, int x2, int y2) {return (x1-x2)*(x1-x2)+(y1-y2)*(y1-y2);}
dnt getcos(int x1, int y1, int x2, int y2, int x3, int y3) {
int a1 = x1-x2, b1 = y1-y2, a2 = x3-x2, b2 = y3-y2;
return (a1*a2+b1*b2)/(dnt)(sqrt(dis(x1, y1, x2, y2))*sqrt(dis(x3, y3, x2, y2)));
}
bool reach(int tarid, int inid, int x, int y) {
dnt cos1 = getcos(ant[inid].x, ant[inid].y, x, y, ant[tarid].x, ant[tarid].y);
dnt cos2 = getcos(ant[inid].x, ant[inid].y, ant[tarid].x, ant[tarid].y, x, y);
if (cos1 < EPS || cos2 < EPS) return false;
dnt t = sqrt(dis(ant[inid].x, ant[inid].y, x, y))*sqrt(1.0-cos1*cos1); return t-0.5 < EPS;
}
struct Laser {
int x, y;
int dist(int id) {return (x-ant[id].x)*(x-ant[id].x)+(y-ant[id].y)*(y-ant[id].y);}
void build(int _x, int _y) {x = _x, y = _y, is_taken[x][y] = true;}
bool can_hit(int id) {return (x-ant[id].x)*(x-ant[id].x)+(dnt)(y-ant[id].y)*(y-ant[id].y) <= r*r;}
int choose_target() {
if (~tar && can_hit(tar)) return tar;
int ret = -1;
for (int i = 0, cur = living[i]; i < (int)living.size(); cur = living[++i])
if (can_hit(cur)) if (ret == -1 || dist(ret) > dist(cur)) ret = cur;
return ret;
}
void attack() {
int id = choose_target(); if (id == -1) return;
for (int i = 0; i < (int)living.size(); i++)
if (reach(id, living[i], x, y)) ant[living[i]].hurt();
}
} laser[10];
void add_age() {for (int i = 0; i < (int)living.size(); i++) ant[living[i]].age++;}
void lose_information() {for (int i = 0; i <= n; i++) for (int j = 0; j <= m; j++) information[i][j] -= (information[i][j] > 0);}
bool game_over() {for (int i = 0; i < (int)living.size(); i++) if (ant[living[i]].steal_cake()) return true; return false;}
bool per_second(int t) {
if (num < 6 && !is_taken[0][0]) ant[cnt].birth(cnt), cnt++;
for (int i = 0; i < (int)living.size(); i++) ant[living[i]].left_information();
for (int i = 0; i < (int)living.size(); i++) ant[living[i]].move();
for (int i = 0; i < (int)living.size(); i++) ant[living[i]].take_cake();
for (int i = 0; i < s; i++) laser[i].attack();
for (int i = 0, cur; i < (int)living.size(); i++) {cur = living[i]; if (ant[cur].cur_blood < 0) ant[cur].dead(), i--;}
if (game_over()) {printf("Game over after %d seconds\n", t), lose = true; return false;}
add_age(), lose_information(); return true;
}
void output() {
printf("%d\n", num);
for (int i = 0, cur = living[i]; i < (int)living.size(); cur = living[++i])
printf("%d %d %d %d %d\n", ant[cur].age, ant[cur].level, ant[cur].cur_blood, ant[cur].x, ant[cur].y);
}
int main() {
scanf("%d%d%d%d%d", &n, &m, &s, &d, &r);
for (int i = 0, x, y; i < s; i++) scanf("%d%d", &x, &y), laser[i].build(x, y);
int t; scanf("%d", &t); for (int i = 1; i <= t; i++) if (!per_second(i)) break;
if (!lose) printf("The game is going on\n"); output(); return 0;
}