BZOJ4256 推箱子

1 篇文章 0 订阅

首先将空地与空地之间连边,转化为一个无向图。


令dp[i][j][0/1/2/3]表示箱子在(i,j),人在箱子旁4个方向中的一个时是否存在方案(和NOIP2013华容道类似)

则有2种转移:dp[i][j][k]->dp[i][j][l](从方向k可以不通过(i,j)到方向l)

   dp[i][j][k]->dp[i'][j'][k](人推动箱子)

最后答案就是枚举所有的(i,j),计算人的合法位置个数。


现在问题的关键就是判联通和计算人的合法位置个数。

这可以用tarjan实现。


时间复杂度O(nm)

#include<bits/stdc++.h>
#define maxn 1010
#define maxm 1001000
#define id(x,y) (((x)-1)*(m)+(y))
using namespace std;
struct edge{int r,nxt;}e[maxm<<3];
struct data{int x,y,v;}q[maxm*4];
int head[maxm],esz,sz,low[maxm],l,r;
int rig[maxm],tim,dfn[maxm],sx,sy,n,m,f[maxn][maxn][4],fa[maxm];
char s[maxn][maxn];
int dx[4]={0,0,1,-1},dy[4]={1,-1,0,0};
void inline addedge(int u,int v){
	e[++esz].r=v;e[esz].nxt=head[u];head[u]=esz;
	e[++esz].r=u;e[esz].nxt=head[v];head[v]=esz;
}
int inline chkin(int x,int y){
	return x>=1&&y>=1&&x<=n&&y<=m;
}
void inline tarjan(int u){
	low[u]=dfn[u]=++tim;
	for(int t=head[u];t;t=e[t].nxt)if(e[t].r!=fa[u])
		if(!dfn[e[t].r]){
			fa[e[t].r]=u,tarjan(e[t].r);
			low[u]=min(low[u],low[e[t].r]);
		} else low[u]=min(low[u],dfn[e[t].r]);
	rig[u]=tim;
}
int get(int u,int v){
	for(int t=head[u];t;t=e[t].nxt)
		if(dfn[e[t].r]>dfn[u]&&dfn[e[t].r]<=dfn[v]&&dfn[v]<=rig[e[t].r])return e[t].r;
	return 0;
}
bool cango(int x,int u,int v){
	if(!dfn[u]||!dfn[v]||!dfn[x])return false;
	int a=get(x,u),b=get(x,v);
	if(a==b||(!a&&!b))return true;
	if(!a&&low[b]<dfn[x])return true;
	if(!b&&low[a]<dfn[x])return true;
	if(low[b]<dfn[x]&&low[a]<dfn[x])return true;
	return false;
}
bool isgood(int x,int y,int k){
	int nx=x+dx[k],ny=y+dy[k];
	if(!chkin(nx,ny)||s[nx][ny]=='#')return false;
	return true;
}
int size(int u){
	return rig[u]-dfn[u]+1;
}
int main(){
//	freopen("A.in","r",stdin);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i)
		scanf("%s",s[i]+1);
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j)if(s[i][j]!='#')
			for(int k=0;k<4;++k){
				int ni=i+dx[k],nj=j+dy[k];
				if(!chkin(ni,nj)||s[ni][nj]=='#')continue;
				if(id(i,j)<id(ni,nj))addedge(id(i,j),id(ni,nj));
			}
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j)
			if(s[i][j]=='X')sx=i,sy=j;
	tarjan(id(sx,sy)),sz=tim;
	for(int i=0;i<4;++i)if(isgood(sx,sy,i))
		q[r++]=data{sx,sy,i},f[sx][sy][i]=1;
	while(l<r){
		data d=q[l++];
		int x=d.x,y=d.y,v=d.v;
		if(isgood(x+dx[v],y+dy[v],v)){
			int nx=x+dx[v],ny=y+dy[v];
			if(!f[nx][ny][v])f[nx][ny][v]=1,q[r++]=data{nx,ny,v};
		}
		for(int i=0;i<4;++i)if(!f[x][y][i]&&isgood(x,y,i)&&cango(id(x,y),id(x+dx[v],y+dy[v]),id(x+dx[i],y+dy[i])))
			f[x][y][i]=1,q[r++]=data{x,y,i};
	}
	long long ans=0;
	int z=id(sx,sy);
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j)if(s[i][j]=='.'){
			int v[6]={0};
			int nans=ans;
			for(int k=0;k<4;++k)if(f[i][j][k])
				for(int l=0;l<4;++l)if(cango(id(i,j),id(i+dx[k],j+dy[k]),id(i+dx[l],j+dy[l])))v[l]=1;
			for(int k=0;k<4;++k)if(v[k]&&isgood(i,j,k)){
				
				int x=id(i+dx[k],j+dy[k]);
				if(!dfn[x])continue;
				if(fa[x]==id(i,j)){
					ans+=size(x);
					if(dfn[x]<=dfn[z]&&dfn[z]<=rig[x])ans--;
				}
				if(low[x]<dfn[id(i,j)])v[4]=1;
			}
			if(v[4]){
				int x=id(i,j);
				ans+=sz-size(x);
				if(dfn[z]<dfn[x]||rig[x]<dfn[z])ans--;
			}
		}
	printf("%lld",ans);		
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值