HDOJ 4083 Three Kingdom Chess【2011 ACM Asia Beijing Regional Problem C】
这两天趁着威哥不在,拿来威哥的机械键盘敲个码量略大点的题爽一下来着,可这一爽就爽了两天啊操,默泪 T_T
这题算法上来说不算是难题,如果知道极大极小搜索和Alpha_Beta剪枝的话算是比较裸的了,减枝也比较容易想到,麻烦的就是模拟的过程了。
去年北京赛后才知道有Alpha_Beta剪枝这个东西,一直拖到今年才学,北京赛的这道题又那么一身的神性,终于给过了。题目意思属于那种看了一半就想把出题人砍了的那种,很多规则:诸葛亮和周瑜下三国棋,轮流走,诸葛亮先;棋盘,有平地、山和水三种;三种类型的兵,步兵、弓箭手和骑兵;骑兵不能上山,三种兵都不能下水;每轮每个人可以选择自己的一个兵走一定步数(步数有输入),走完后可以再攻击一下,走的时候不能经过敌人,可以经过同伴但不能和同伴停在同一格,当然不能经过自己不能走的地形;攻击有范围,看图就知道了,对方的力量掉一定伤害,伤害是自己的力量*一个系数,系数按图中顺序为2,逆序0.5(向下取整),同类型打为1;好像没别的规则了,最多k次游戏结束,问你结束时最高的力量差(诸葛亮的减周瑜),当然两人都是神,一点错误都不会犯的那种。
方法也不用怎么想,就是极大极小的框架然后暴搜加剪枝,Alpha_Beta剪枝不用说,肯定要的;状态山大,没法记忆化就不去记忆化了,有层数限制,用处也不会大;搜索时排序剪枝也容易想到,写起来略麻烦。这么几个剪枝下来,如果没敲错,没看错输入里面和图上是刚好颠倒着的话就可以过了,嗯,应该可以过了,这题也不那么卡时间,嗯?我为什么TLE那么多次?啊啊啊啊,k&1==0里面&优先级比等号低啊,少了个括号然后几个剪枝就完全没效果了啊啊啊啊啊。还一个剪枝也容易想到,至少在调的时候就可以发现这里跑了很多没用的状态了,就是如果后面的操作已经非常完美了(你把剩下的敌人全部无伤干掉了)就返回了(不知道是写错了还是怎么的,貌似只快了100ms+)。还有一点,开始时候我让他走到一个点之后如果能打就打了,把不能打的状态删掉了,然后就一跪不起了,看下面这种吧:
##...
raF..
#####
大写的是诸葛亮的,小写的是周瑜的,#是水,比如说F都是力量超高的,r比F还要高的力量,a是弱菜:这时候r出不去也打不着,F要能挨啊,如果把a打死了,r被放出来,后果不堪设想啊。当然对r来说,那什么,猪一样的同伴...
虽然说麻烦程度胜似模拟了吧,不过错这么多次是要闹哪样啊,代码能力跪舔到极限了啊,码跑的不快,倒数,再不想碰了:
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<cstdlib> 5 using namespace std; 6 7 int ratk[12][2]={ 8 {-1,0},{0,-1},{0,1},{1,0}, 9 {-1,-1},{-1,1},{1,-1},{1,1}, 10 {-2,0},{0,-2},{0,2},{2,0} 11 }; 12 int natk[3]={8,12,4}; 13 double fatk[3][3]={{1,2,0.5},{0.5,1,2},{2,0.5,1}}; 14 int dir[4][2] = {{-1,0},{1,0},{0,-1},{0,1}}; 15 16 struct Soldier{ 17 int flag, type; 18 Soldier(){} 19 Soldier(int f, int t):flag(f), type(t){} 20 }sld[8]; 21 22 struct Next{ 23 int id, mx, my, aid, sc; 24 25 Next(){} 26 Next(int _id, int _mx, int _my, int _aid, int _sc): 27 id(_id), mx(_mx), my(_my), aid(_aid), sc(_sc){} 28 29 friend bool operator< (const Next& a, const Next& b){ 30 return a.sc > b.sc; 31 } 32 }nexts[12][150]; //每层到下一层的走法,这里都开好了不用递归时临时开了 33 int nsz[12]; 34 35 struct State{ 36 int x, y, sc; 37 State(){} 38 State(int _x, int _y, int _sc): 39 x(_x), y(_y), sc(_sc){} 40 }sta[12][8]; 41 short smaze[12][8][8]; //递归最多10层,每层一个状态就不用临时开了 42 43 int lx, ly, lk; 44 int n, s[3]; 45 short maze[8][8]; 46 47 int skm, szy; 48 49 inline bool myturn(int k, int id){ //判断是不是轮到他一方的走 50 if(sld[id].flag!=(k&1)) return false; 51 if(sta[k][id].sc==0) return false; 52 return true; 53 } 54 55 inline bool moveillegal(int k, int id, int x, int y){ //移动位置是否无效 56 if(x<0||x>=lx) return true; 57 if(y<0||y>=ly) return true; 58 if(maze[x][y]==2) return true; 59 if(sld[id].type==2&&maze[x][y]==1) return true; 60 if(smaze[k][x][y]!=-1&&sld[smaze[k][x][y]].flag!=sld[id].flag) return true; 61 return false; 62 } 63 64 inline bool attackillegal(int k, int id, int x, int y){ //攻击位置是否无效 65 if(x<0||x>=lx) return true; 66 if(y<0||y>=ly) return true; 67 if(smaze[k][x][y]==-1) return true; 68 if(sld[smaze[k][x][y]].flag==sld[id].flag) return true; 69 70 if(sta[k][smaze[k][x][y]].sc==0) return true; 71 return false; 72 } 73 74 inline void donext(int k, Next& p){ //修改状态 75 if(p.id==-1) return; //没走也没攻击 , 就不修改了 76 swap(smaze[k][sta[k][p.id].x][sta[k][p.id].y],smaze[k][p.mx][p.my]); //修改走法 77 sta[k][p.id].x = p.mx; 78 sta[k][p.id].y = p.my; 79 80 if(p.aid==-1) return; 81 sta[k][p.aid].sc-=p.sc; //修改攻击状态 82 sld[p.aid].flag?szy-=p.sc:skm-=p.sc; 83 if(sta[k][p.aid].sc==0){ 84 smaze[k][sta[k][p.aid].x][sta[k][p.aid].y]=-1; 85 } 86 } 87 88 inline void undonext(int k, Next& p){ //恢复状态 89 if(p.id==-1) return; //没走也没攻击 90 sta[k][p.id].x = sta[k-1][p.id].x; //恢复走法 91 sta[k][p.id].y = sta[k-1][p.id].y; 92 swap(smaze[k][sta[k][p.id].x][sta[k][p.id].y],smaze[k][p.mx][p.my]); 93 94 if(p.aid==-1) return; 95 sta[k][p.aid].sc+=p.sc; //恢复攻击 96 sld[p.aid].flag?szy+=p.sc:skm+=p.sc; 97 smaze[k][sta[k][p.aid].x][sta[k][p.aid].y]=p.aid; 98 } 99 100 inline int attack(int k, int sid, int tid){ //计算攻击血量 101 return min(sta[k][tid].sc, 102 int(sta[k][sid].sc*fatk[sld[sid].type][sld[tid].type])); 103 } 104 105 inline void getattack(int k, int id, int x, int y){ //枚举一个移动的攻击,并把移动+攻击的方法塞到Next数组里 106 for(int d=0;d<natk[sld[id].type];d++){ 107 int tx = x + ratk[d][0]; 108 int ty = y + ratk[d][1]; 109 if(!attackillegal(k, id, tx, ty)){ 110 int atkid = smaze[k][tx][ty]; 111 int atksc = attack(k, id, atkid); 112 nexts[k][nsz[k]++] = Next(id, x, y, atkid, atksc); 113 } 114 } 115 } 116 117 bool reach[8][8]; 118 inline void stepbystep(int dep, int k, int id, int x, int y){ //递归标记可移动到的位置 119 reach[x][y] = true; 120 if(dep==0) return; 121 for(int d=0;d<4;d++){ 122 int tx = x+dir[d][0]; 123 int ty = y+dir[d][1]; 124 if(!moveillegal(k, id, tx, ty)){ 125 stepbystep(dep-1, k, id, tx, ty); 126 } 127 } 128 } 129 130 inline void getmove(int k, int id){ //枚举走法 131 memset(reach, false, sizeof(reach)); 132 stepbystep(s[sld[id].type], k, id, sta[k][id].x, sta[k][id].y); 133 for(int i=0;i<lx;i++){ 134 for(int j=0;j<ly;j++){ 135 if(reach[i][j]&&smaze[k][i][j]==-1){ 136 getattack(k, id, i, j); //对每一个走法去枚举攻击方法 137 nexts[k][nsz[k]++] = Next(id, i, j, -1, 0); //不进行攻击的走法 138 } 139 } 140 } 141 getattack(k, id, sta[k][id].x, sta[k][id].y); //不移动的攻击方法 142 } 143 144 inline void getnext(int k){ //得到下一步的所有状态 145 nsz[k]=0; 146 for(int i=0;i<n;i++){ 147 if(myturn(k,i)) getmove(k, i); //对一个棋子枚举走法 148 } 149 nexts[k][nsz[k]++] = Next(-1, -1, -1, -1, 0); //所有棋子都不移动也不攻击 150 sort(nexts[k], nexts[k]+nsz[k]); //按攻击血量排序 151 } 152 153 void debug(int k){ //调试用的 154 for(int i=0;i<lx;i++){ 155 for(int j=0;j<ly;j++){ 156 if(~smaze[k][i][j]){ 157 if(sld[smaze[k][i][j]].flag==0){ 158 switch(sld[smaze[k][i][j]].type){ 159 case 0: putchar('F'); break; 160 case 1: putchar('A'); break; 161 case 2: putchar('R'); break; 162 } 163 } else if(sld[smaze[k][i][j]].flag==1){ 164 switch(sld[smaze[k][i][j]].type){ 165 case 0: putchar('f'); break; 166 case 1: putchar('a'); break; 167 case 2: putchar('r'); break; 168 } 169 } 170 } else { 171 switch(maze[i][j]){ 172 case 0: putchar('.'); break; 173 case 1: putchar('*'); break; 174 case 2: putchar('#'); break; 175 } 176 } 177 } puts(""); 178 } 179 printf("%d %d\n", skm, szy); 180 getchar(); 181 } 182 183 void shownext(int k, int ti){ //调试用的 184 for(int i=0;i<nsz[k];i++){ 185 if(ti==i) printf("%d>", k); 186 Next &u = nexts[k][i]; 187 printf("%d mov: %d %d, atk: %d %d\n", u.id, u.mx, u.my, u.aid, u.sc); 188 } 189 } 190 191 int dfs(int k, int sc, int maxcut, int mincut){ 192 //debug(k); 193 194 if(k==lk) return sc; //k步了 195 if(szy==0||skm==0) return sc; //一个人死光了 196 197 getnext(k); //得到下一步走法 198 memcpy(sta[k+1],sta[k],sizeof(sta[k])); 199 memcpy(smaze[k+1],smaze[k],sizeof(smaze[k])); //把上一层状态复制过来 200 int maxval = sc-szy, minval = sc+skm; 201 for(int i=0;i<nsz[k];i++){ 202 //shownext(k, i); 203 Next &u = nexts[k][i]; 204 donext(k+1, u); //递归过程 205 int val = dfs(k+1,(k&1)?(sc-u.sc):(sc+u.sc), maxval, minval); 206 undonext(k+1, u); 207 if(maxval<val) maxval = val; 208 if(minval>val) minval = val; 209 if(((k&1)==0)&&val>=mincut) return val; //Alpha_Beta剪枝 210 if(((k&1)==0)&&(val-sc>=szy)) return val; //已经是完美走法的剪枝 211 if(((k&1)==1)&&val<=maxcut) return val; //Alpha_Beta剪枝 212 if(((k&1)==1)&&(val-sc<=-skm)) return val; //已经是完美走法的剪枝 213 } 214 return (k&1)?minval:maxval; //极大极小过程 215 } 216 217 int main(){ 218 while(scanf("%d%d%d", &lx, &ly, &lk),lx||ly){ 219 memset(maze, 0, sizeof(maze)); 220 memset(smaze, -1, sizeof(smaze)); 221 for(int i=0;i<lx;i++){ 222 for(int j=0;j<ly;j++){ 223 scanf("%d", &maze[i][j]); 224 } 225 } 226 scanf("%d%d%d%d", &n, &s[0], &s[2], &s[1]); 227 szy=skm=0; 228 for(int i=0;i<n;i++){ 229 int x, y, a, b, c; 230 scanf("%d%d%d%d%d", &x, &y, &a, &b, &c); 231 if(c==1) c=2; else if(c==2) c=1; //操 232 x--; y--; 233 sld[i] = Soldier(a, c); 234 smaze[0][x][y] = i; 235 sta[0][i] = State(x, y, b); 236 a?szy+=b:skm+=b; 237 } 238 int ans = dfs(0, skm-szy, -szy, skm); 239 printf("%d\n", ans); 240 } 241 }
ps:晒代码里面那个好像是标称,不过有bug,找了一组数据
3 3 6 0 0 0 0 2 0 0 0 0 5 2 1 1 2 1 1 116 1 2 3 0 125 1 1 1 1 134 2 3 1 0 143 0 3 3 1 161 2
乱构造的数据,不过代码确实是错了,还为了这个数据查了半天