BZOJ 1164 Baltic2008 Game

1 篇文章 0 订阅
1 篇文章 0 订阅

提示:
1. 为什么不一定是先手必胜啊? 哦对了 , 还可以跳过对手多走一步
2. 上述情况最多只能发生多少次呢?
3. 裸的状态表示方式会挂掉 , 但其实很多状态都是无用的 , 最好能把这图分成阶段图。

实在没思路可以读读论文

代码后有详细说明:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <string>
#include <vector>
#include <deque>
#include <stack>
#include <queue>
#include <algorithm>

using namespace std;
const int maxn = 310;
const int dx[]={0,0,1,-1};
const int dy[]={1,-1,0,0};

struct points { int x , y; points(int x=0 , int y=0):x(x),y(y){} };

int t , n;
char g[maxn][maxn];
int d[maxn][maxn] , d1[maxn][maxn] , d2[maxn][maxn];
int num[maxn][maxn];
vector<points> dic[maxn*maxn];

bool dp[2][maxn][maxn][2];
bool abj(points a , points b) { return (a.x==b.x && abs(a.y-b.y)==1) || (a.y==b.y && abs(a.x-b.x)==1); }
bool judge(int x , int y) { return x>=1 && x<=n && y>=1 && y<=n; }

void bfs(int ax , int ay , int bx , int by ,int d[maxn][maxn])
{
    for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) d[i][j] = -1;
    queue<points> q;
    d[ax][ay] = 0;
    q.push(points(ax, ay));

    while(!q.empty())
    {
        points now = q.front() , ne; q.pop();
        int dis = d[now.x][now.y];
        for(int i=0;i<4;i++)
        {
            ne = now;
            ne.x+= dx[i];
            ne.y+= dy[i];
            if(!judge(ne.x, ne.y) || g[ne.x][ne.y]=='#') continue;
            if(d[ne.x][ne.y]==-1)
            {
                d[ne.x][ne.y] = dis+1;
                q.push(ne);
            }
        }
    }
}

void solve()
{
    int ax , ay , bx , by;
    for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) 
        if(g[i][j]=='A') ax = i , ay = j; else if(g[i][j]=='B') bx = i , by = j;

    bfs(ax, ay, bx, by, d1);
    bfs(bx, by, ax, ay, d2);
    for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) 
        if(d1[i][j]!=-1 && d1[i][j]+d2[i][j]==d1[bx][by]) d[i][j] = d1[i][j]; 
        else d[i][j] = -1;

    for(int i=0;i<=n*n;i++) dic[i].clear();
    for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(d[i][j]!=-1)
        num[i][j] = dic[d[i][j]].size() , dic[d[i][j]].push_back(points(i , j));
//  for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) cout<<d[i][j]<<(j==n?"\n":" ");
//  for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) cout<<num[i][j]<<(j==n?"\n":" ");

    int len = d[bx][by] , cur = 0 , mid = len/2;
    if(len%2) { puts("A"); return; }

    for(int i=0;i<dic[mid].size();i++) for(int j=0;j<dic[len-mid+1].size();j++)
        dp[cur][i][j][1] = abj(dic[mid][i], dic[len-mid+1][j]);// , cout<<i<<" "<<j<<" "<<dp[cur][i][j][1]<<endl ;

    for(int i=mid-1;i>=0;i--) 
    {
        cur = 1-cur;
        // update dp[cur][k][l][j]

        for(int j=0;j<2;j++) for(int k=0;k<dic[i].size();k++) for(int l=0;l<dic[len-i+j].size();l++)
        {
            bool& now = dp[cur][k][l][j] = false;
            for(int move=0;move<4;move++)
            {
                int nx = dx[move] , ny = dy[move];
                if(j) nx += dic[len-i+j][l].x , ny+= dic[len-i+j][l].y;
                else  nx += dic[i][k].x , ny += dic[i][k].y;
                if(!judge(nx, ny)) continue;

                bool ok;
                if(!j) ok = (d[nx][ny] == i+1); else ok = (d[nx][ny] == len-i+j-1);
                if(ok) 
                {
                    if(j) now |= !dp[cur][k][num[nx][ny]][1-j];
                    else  now |= !dp[1-cur][num[nx][ny]][l][1-j];
                }
            }
        }
    }
    puts(dp[cur][num[ax][ay]][num[bx][by]][0]?"A":"B");
}

int main(int argc, char *argv[]) {

    cin>>t;

    while(t--)
    {
        cin>>n;
        for(int i=1;i<=n;i++) scanf("%s" , g[i]+1);

        solve() ;
    }

    return 0;
}

论文里其实已经讲的很详细啦 , 这里就补充几点吧:

  1. 分阶段的时候要从两个方向来找 , 也就是两次BFS , 来确保走到的每一步都是最短路
  2. 考虑到有极端情况 , 比如所论文中所提到的 , 所以这里我用了滚动数组
  3. 其实即使你不知道在中途就可以决断胜负也是可以做的 , 再记录一个是否跳跃这样一个信息就可以啦(但显然这样做并不理想)。 但这说明了一点 , 这个题的做法其实就是尝试去优化无用状态 , 人工分成若干阶段还是很常见的。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值