题意:给定一个w*h图,'#'可以走,'.'不可以走,且保证任意两个可达的'#'之间有且只有一条路,给出q个询问(x1, y1, x2, y2),问位于(x1, y1)的'#'要到(x2, y2)的'#'要转过几个弯
思路:有题易知,图可以看成一棵一棵的树,问题就可以变成求树上两点间距离,很容易求出点(x1, y1)到其树根节点(x0, y0)需要转几个弯,现在要考虑的问题是:已知两点到根的“距离”和它们的公共祖先,如何求出它们之间的“距离”。
画图更利于理解:设公共祖先为f, 两点为u, v
1. 计算u和v到f的距离:如果公共祖先f在其父亲fa的左(右,上,下)边,而公共祖先f恰好也是要向左(右,上,下)边才能走到u,则它们之间的距离为dis[u]-dis[f],如下图绿点所示,起到f的距离为0; 否则,就为dis[u]-dis[f]-1,相当于减去fa到f的那次转弯,如下图红点所示,起到f的距离为1;v的计算同理
2. 合并u到f和f到v得路径,计算距离:记u在f的d1方向,v在f的d2方向,如果两方向垂直且u,v都不为公共祖先f,则dis(u, v)=dis(u, f)+dis(f, v)+1,相当于它们的路程在公共祖先f处应该拐一个弯,也可以用下图解释,绿点到红点的距离为2.
然后,又产生了问题——如何判断某点在其某一祖先的哪个方向?Moor采用的方法是记录下点的每个点的由其父亲转移过来的方向pdir,在tarjan的时候,记录下tarjan时每个点进入深搜时的方向nowd,在发现可以找到公共祖先时,这条路径上的每个点的nowd恰好对应了这条路径,可以利用这个判断;SS赛后也想了一个方法,在dfs的时候给标了一个号,这样可以保证按照dfs的原理,以某一点为根dfs下去,若其叶子的遍历顺序分别为(l1, l2, l3, ....),则点到l1路径上所有点的标号<=点到l2路径上所有点的标号<=点到l3路径上所有点的标号,利用这个性质,可以枚举四个方向判断点u,v在f的哪条链(方向)上,两个方法的速度是一样的......
上代码:Moor的解法
#include <iostream> #include <cstdio> #include <cstring> #define MAXN 100010 using namespace std; char **ma; int **ind; int fa[MAXN]; const int dir[4][2]={{0,1},{1,0},{0,-1},{-1,0}}; int w,h,he[MAXN],to[MAXN],nex[MAXN],co[MAXN],stop; int nee[MAXN],ans[MAXN]={0}; int nowd[MAXN]={0},pdir[MAXN]={0}; bool vi[MAXN]={0}; inline bool check(int a,int b) { if((a+1)%4==b||(a+3)%4==b) return false; return true; } void dfs(int x,int y,int f,int num,int pre) { nee[ind[x][y]]=num; pdir[ind[x][y]]=pre; for(int i=0;i<4;++i) { int nx=x+dir[i][0],ny=y+dir[i][1],nnum=num; if(ma[nx][ny]=='#'&&ind[nx][ny]!=f) { if(pre!=5&&!check(pre,i)) ++nnum; dfs(nx,ny,ind[x][y],nnum,i); } } } void add(int a,int b,int c) { to[stop]=b; nex[stop]=he[a]; co[stop]=c; he[a]=stop++; } pair<int,int> mfind(int n) { if(fa[n]!=n) { pair<int,int> pa=mfind(fa[n]); if(pa.second==1) fa[n]=pa.first; else pa.first=n,pa.second=1; return pa; } return make_pair(n,0); } void tarjan(int x,int y,int f,int pre) { for(int i=0;i<4;++i) { int nx=x+dir[i][0],ny=y+dir[i][1]; nowd[ind[x][y]]=i; if(ma[nx][ny]=='#'&&ind[nx][ny]!=f) { tarjan(nx,ny,ind[x][y],i); fa[ind[nx][ny]]=ind[x][y]; } } for(int i=he[ind[x][y]];i!=-1;i=nex[i]) if(vi[to[i]]) { pair<int,int> tpa=mfind(to[i]); int ff=fa[tpa.first]; ans[co[i]]=nee[ind[x][y]]+nee[to[i]]-2*nee[ff]; if(ff!=ind[x][y]) { if(check(pdir[tpa.first],nowd[ff])) ans[co[i]]-=2; else if(ff==1) ++ans[co[i]]; } else if(pre!=5&&!check(pre,pdir[tpa.first])) ans[co[i]]-=1; } vi[ind[x][y]]=1; } int main() { //freopen("/home/moor/Code/input.txt","r",stdin); scanf("%d%d",&w,&h); ma=new char* [w+3]; ind=new int *[w+2]; int top=1,q; stop=1; memset(he,-1,sizeof(he)); for(int i=0;i<MAXN;++i) fa[i]=i; for(int i=0;i<=w+1;++i) { ma[i]=new char[h+3]; ind[i]=new int[h+3]; memset(ma[i],'\0',sizeof(char)*(h+3)); memset(ind[i],0,sizeof(int)*(h+3)); } for(int i=1;i<=w;++i) { scanf("%s",&ma[i][1]); for(int j=1;j<=h;++j) { if(ma[i][j]=='#') ind[i][j]=top++; } } scanf("%d",&q); for(int i=0;i<q;++i) { int a,b,xx1,xx2,yy1,yy2; scanf("%d%d%d%d",&xx1,&yy1,&xx2,&yy2); a=ind[xx1][yy1],b=ind[xx2][yy2]; add(a,b,i+1); add(b,a,i+1); } for(int i=1;i<=w;++i) for(int j=1;j<=h;++j) { if(ma[i][j]!='#'||vi[ind[i][j]]) continue; dfs(i,j,-1,0,5); tarjan(i,j,-1,5); } for(int i=1;i<=q;++i) printf("%d\n",ans[i]); return 0; }SS的做法:
#include <iostream> #include <cstdio> #include <cstring> #include <cstdlib> #define MAXN 100010 using namespace std; char **ma; int **ind; int cnt, fa[MAXN]; const int dir[4][2]={{1,0},{0,1},{-1,0},{0,-1}}; int w, h, q, he[MAXN], to[MAXN], nex[MAXN], co[MAXN], stop; int dis[MAXN], ans[MAXN]; int from[MAXN]; bool vi[MAXN]; struct node { int x, y; }id[MAXN]; void dfs(int x,int y,int f,int num,int pre) { ind[x][y]=++cnt; id[cnt].x=x, id[cnt].y=y; dis[ind[x][y]]=num; from[ind[x][y]]=pre; for(int i=0;i<4;++i) { int nx=x+dir[i][0], ny=y+dir[i][1]; if (ma[nx][ny]=='#'&&ind[nx][ny]!=f) dfs(nx,ny,ind[x][y],num+(pre!=5&&i!=pre), i); } } void add(int a,int b,int c) { to[stop]=b; nex[stop]=he[a]; co[stop]=c; he[a]=stop++; } int find(int x) { if (fa[x]==x) return x; fa[x]=find(fa[x]); return fa[x]; } void tarjan(int x,int y,int f) { for (int i=0;i<4;++i) { int nx=x+dir[i][0], ny=y+dir[i][1]; if (ma[nx][ny]=='#'&&ind[nx][ny]!=f) { tarjan(nx,ny,ind[x][y]); fa[ind[nx][ny]]=ind[x][y]; } } vi[ind[x][y]]=1; for(int i=he[ind[x][y]];i!=-1;i=nex[i]) if(vi[to[i]]) ans[co[i]]=find(to[i]); } int cal(int f, int a, int *t) { int tmp=-1; if (f==a) return 0; int res=dis[a]-dis[f]; int x=id[f].x, y=id[f].y; for (int i=0; i<4; i++) { int xx=x+dir[i][0], yy=y+dir[i][1]; if (ma[xx][yy]=='#' && ind[xx][yy]<=a && ind[xx][yy]!=f && ind[xx][yy]>tmp) { *t=i; tmp=ind[xx][yy]; } } if (from[f]!=5&&(*t)!=from[f]) res--; return res; } int main() { //freopen("data.in","r",stdin); scanf("%d%d",&w,&h); ma=new char* [w+3]; ind=new int *[w+2]; cnt=stop=0; memset(he,-1,sizeof(he)); for(int i=0;i<MAXN;++i) fa[i]=i; for(int i=0;i<=w+1;++i) { ma[i]=new char[h+3]; ind[i]=new int[h+3]; memset(ma[i],'\0',sizeof(char)*(h+3)); memset(ind[i],0,sizeof(int)*(h+3)); } for(int i=1;i<=w;++i) scanf("%s",&ma[i][1]); scanf("%d",&q); for(int i=1;i<=w;i++) for(int j=1;j<=h;j++) { if (ma[i][j]!='#'||ind[i][j]) continue; dfs(i,j,-1,0,5); } for(int i=1;i<=q;++i) { int a,b,xx1,xx2,yy1,yy2; scanf("%d%d%d%d",&xx1,&yy1,&xx2,&yy2); a=ind[xx1][yy1], b=ind[xx2][yy2]; add(a,b,i); add(b,a,i); } for(int i=1;i<=w;++i) for(int j=1;j<=h;++j) { if(ma[i][j]!='#'||vi[ind[i][j]]) continue; tarjan(i,j,-1); } stop=0; for(int i=1;i<=q;++i) { int a=to[stop++], b=to[stop++], f=ans[i], xa, xb; printf("%d\n", cal(f, a, &xa)+cal(f, b, &xb)+(f!=a&&f!=b&&((xa&1)!=(xb&1)))); } return 0; }