题意:给定一个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;
}