[BZOJ Contest-2017省队十连测推广赛2·T3][BZOJ4256][DP][Tarjan]推箱子

3 篇文章 0 订阅

老师找来的题解
这里摘一下好了…并不知道出题人是谁

考虑相邻的两块空地之间连边,这样就得到一个无向图,在这张图上DP
在最后时刻,箱子肯定在目标处,人在箱子的四周的其中一个空地上
倒过来考虑就有两种情况
1. 人动箱子不动。
2. 如果人在箱子4周相邻的格子中,人拉着箱子动。

令f[i][j][0..3]表示是否存在箱子在(i,j)人在箱子的其中一侧(0..3表示在那一侧)的方案。
那么f[sx][sy][0..3]=1,(sx,sy)为目标坐标,当然要保证箱子和人所处的位置都不是障碍。

因为我们已经把网格图转成普通图,假设人在x点,箱子在y点,第一种情况就相当于求去掉y点后,与x联通的点的个数,第二种情况就是f的转移,存在方案人在x,箱子在y,如果去掉y点,x与z联通,那么存在人在z,箱子在y的方案。

用tarjan可以解决
然而tarjan要打非递归…

2017.11.23 UPD:感谢 @yfzcsc 大佬hack了我的代码…原来的126~128行打错了……

#include <cstdio>
#include <iostream>
#include <algorithm>
#define N 1010
#define M 2100010
#define ID(x,y) ((x-1)*m+y)

using namespace std;

typedef long long ll;

int n,m,g,cnt,top,stx,sty,st;
int low[M],dfn[M],G[M],fa[M],L[M],R[M],cur[M];
const int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1};
int Q[M<<1][3],S[M<<1];
int f[N][N][4];
char A[N][N];
ll Ans;
struct edge{
  int t,nx;
}E[M<<2];

void tarjan(int x){
  /*low[x]=dfn[x]=++g;
  L[x]=g;
  for(int i=G[x];i;i=E[i].nx)
    if(E[i].t!=fa[x]){
      if(!dfn[E[i].t]) fa[E[i].t]=x,tarjan(E[i].t),low[x]=min(low[x],low[E[i].t]);
      else low[x]=min(low[x],dfn[E[i].t]);
    }
    R[x]=g;*/
  int top=0;
  S[top=1]=st; low[st]=dfn[st]=L[st]=++g;
  for(int i=1;i<=n*m;i++) cur[i]=G[i];
  while(top){
    int x=S[top];
    for(;cur[x];cur[x]=E[cur[x]].nx)
      if(!dfn[E[cur[x]].t]){
    int v=E[cur[x]].t;
    S[++top]=v;
    low[v]=dfn[v]=L[v]=++g;
    break;
      }
      else low[x]=min(dfn[E[cur[x]].t],low[x]);
    if(!cur[x]){
      R[x]=g; top--; 
      fa[x]=S[top];
      if(!top) continue;
      low[S[top]]=min(low[x],low[S[top]]);
      cur[S[top]]=E[cur[S[top]]].nx;
    }
  }
}

inline void Insert(int x,int y){
  E[++cnt].t=y; E[cnt].nx=G[x]; G[x]=cnt;
}

inline int inson(int x,int y){
  for(int i=G[x];i;i=E[i].nx)
    if(fa[E[i].t]==x&&dfn[y]>=L[E[i].t]&&dfn[y]<=R[E[i].t]) return E[i].t;
  return -1;
}

inline bool linked(int f,int x,int y){
  if(!dfn[x]||!dfn[y]) return false;
  int px=inson(f,x),py=inson(f,y);
  if(px==-1&&py==-1) return true;
  if(px!=-1&&py!=-1)
    return px==py||low[px]<dfn[f]&&low[py]<dfn[f];
  if(px==-1) return low[py]<dfn[f];
  else return low[px]<dfn[f];
}

#define Size(x) (R[x]-L[x]+1)

inline int calc(int x,int y){
  int p=inson(x,y);
  if(low[p]>=dfn[x]&&~p) return Size(p);
  int ret=Size(st)-1;
  for(int i=G[x];i;i=E[i].nx)
    if(fa[E[i].t]==x&&low[E[i].t]>=dfn[x]) ret-=Size(E[i].t);
  return ret;
}

int main(){
  scanf("%d%d",&n,&m);
  for(int i=0;i<=m+1;i++) A[n+1][i]=A[0][i]='#';
  for(int i=1;i<=n;i++)
    scanf("%s",A[i]+1),A[i][0]=A[i][m+1]='#';
  for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
      if(A[i][j]!='#'){
    if(A[i][j]=='X') stx=i,sty=j;
    for(int k=0;k<4;k++)
      if(A[i+dx[k]][j+dy[k]]!='#') Insert(ID(i,j),ID(i+dx[k],j+dy[k]));
      }
  st=ID(stx,sty);
  tarjan(st);
  for(int i=0;i<4;i++)
    if(A[stx+dx[i]][sty+dy[i]]!='#'){
      f[stx][sty][i]=1;
      Q[++top][0]=stx,Q[top][1]=sty,Q[top][2]=i;
    }
  for(int i=1;i<=top;i++){
    int x=Q[i][0],y=Q[i][1],p=Q[i][2],ax=x+dx[p],ay=y+dy[p];
    if(A[ax+dx[p]][ay+dy[p]]!='#'&&!f[ax][ay][p]){
      f[ax][ay][p]=1;
      Q[++top][0]=ax;Q[top][1]=ay;Q[top][2]=p;
    }
    for(int j=0;j<4;j++){
      int bx=x+dx[j],by=y+dy[j];
      if(j!=p&&linked(ID(x,y),ID(ax,ay),ID(bx,by))&&A[bx][by]!='#'&&!f[x][y][j]){
    f[x][y][j]=1;
    Q[++top][0]=x;Q[top][1]=y;Q[top][2]=j;
      }
    }
  }
  for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
      if(A[i][j]=='.'){
    int x=ID(i,j),x0=ID(i+dx[0],j+dy[0]),x1=ID(i+dx[1],j+dy[1]),x2=ID(i+dx[2],j+dy[2]),x3=ID(i+dx[3],j+dy[3]);
    if(f[i][j][0]) Ans+=calc(x,x0);
    if(f[i][j][1]&&(!linked(x,x0,x1) || !f[i][j][0])) Ans+=calc(x,x1);
    if(f[i][j][2]&&(!linked(x,x0,x2) || !f[i][j][0]) && (!linked(x,x1,x2) || !f[i][j][1])) Ans+=calc(x,x2);
    if(f[i][j][3]&& (!linked(x,x0,x3) || !f[i][j][0])&& (!linked(x,x1,x3) || !f[i][j][1])&&(!linked(x,x2,x3) || !f[i][j][2])) Ans+=calc(x,x3);
    for(int k=0;k<4;k++)
      if(f[i][j][k]&&linked(x,st,ID(i+dx[k],j+dy[k]))){ Ans--; break; }
      }
  printf("%lld\n",Ans);
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值