BZOJ 4242: 水壶 Kruskal+BFS
4242: 水壶
Time Limit: 40 Sec Memory Limit: 256 MBSubmit: 427 Solved: 112
[Submit][Status][Discuss]
Description
JOI君所居住的IOI市以一年四季都十分炎热著称。
IOI市是一个被分成纵H*横W块区域的长方形,每个区域都是建筑物、原野、墙壁之一。建筑物的区域有P个,编号为1...P。
JOI君只能进入建筑物与原野,而且每次只能走到相邻的区域中,且不能移动到市外。
JOI君因为各种各样的事情,必须在各个建筑物之间往返。虽然建筑物中的冷气设备非常好,但原野上的日光十分强烈,因此在原野上每走过一个区域都需要1单位的水。此外,原野上没有诸如自动售货机、饮水处之类的东西,因此IOI市的市民一般都携带水壶出行。大小为x的水壶最多可以装x单位的水,建筑物里有自来水可以将水壶装满。
由于携带大水壶是一件很困难的事情,因此JOI君决定携带尽量小的水壶移动。因此,为了随时能在建筑物之间移动,请你帮他写一个程序来计算最少需要多大的水壶。
现在给出IOI市的地图和Q个询问,第i个询问(1<=i<=Q)为“在建筑物Si和Ti之间移动,最小需要多大的水壶?”,请你对于每个询问输出对应的答案。
Input
第一行四个空格分隔的整数H,W,P,Q,表示IOI市被分成了纵H*横W块区域,有P个建筑物,Q次询问。
接下来H行,第i行(1<=i<=H)有一个长度为W的字符串,每个字符都是’.’或’#’之一,’.’表示这个位置是建筑物或原野,’#’表示这个位置是墙壁。
接下来P行描述IOI市每个建筑物的位置,第i行(1<=i<=P)有两个空格分隔的整数Ai和Bi,表示第i个建筑物的位置在第Ai行第Bi列。保证这个位置在地图中是’.’
接下来Q行,第i行(1<=i<=Q)有两个空格分隔的整数Si和Ti,表示第i个询问为“在建筑物Si和Ti之间移动,最小需要多大的水壶?”
Output
输出Q行,第i行(1<=i<=Q)一个整数,表示在建筑物Si和Ti之间移动最小需要多大的水壶。如果无法到达,输出-1。此外,如果不需要经过原野就能到达,输出0。
Sample Input
5 5 4 4
.....
..##.
.#...
..#..
.....
1 1
4 2
3 3
2 5
1 2
2 4
1 3
3 4
.....
..##.
.#...
..#..
.....
1 1
4 2
3 3
2 5
1 2
2 4
1 3
3 4
Sample Output
3
4
4
2
4
4
2
HINT
1<=H<=2000
1<=W<=2000
2<=P<=2*10^5
1<=Q<=2*10^5
1<=Ai<=H(1<=i<=P)
1<=Bi<=W(1<=i<=P)
(Ai,Bi)≠(Aj,Bj)(1<=i<j<=P)
1<=Si<Ti<=P(1<=i<=Q)
Source
JOI 2013~2014 春季training合宿 竞技2 By PoPoQQQ
朴素的暴力:得到任意两点的间的最短距离,然后kruskal。因为是求最大值,所以可以直接用并查集(按秩合并)上的路径最值。树高为O(logP)。共O(P*W*H)
考虑慢慢增加每个建筑物连出去的边权,这个和Kruskal很相似。然后又一个朴素的想法,搞P个队列,同时扩展。碰到一个建筑物才停下来并不扩展这个建筑物,合并这个两个建筑物。O(P*W*H)
既然是同时扩展,那么边权肯定都一样。那么碰到一个已经被扩展的点就可以将距离加起来,用来更新当成这个两个建筑物的距离。边权都*2肯定还是满足kruskal的算法。一个点最多被碰四次。O(W*H*4)
细节:可能边权并没有*2,而是一个奇一个偶,可能会影响同一种边权的kruskal加边顺序,所以我把这些边都存下来......
然后求一发Kruskal就可以了。
#include<cstdio> #include<cstring> #include<algorithm> const int HW(2010),len(200000),N(4000000); const int dx[]={0,0,-1,1}; const int dy[]={-1,1,0,0}; int O[HW][HW],last[HW][HW],dis[HW][HW],H,W,P,Q; struct Palce{int x,y;}q[N+10],now; int l,h; char ch[HW];int x,y; struct Node{int a,b,c;}bot[N+10];int top; int f[len+10],cost[len+10],rank[len+10],depth[len+10]; bool cmp(Node A,Node B){return A.c<B.c;} int max(int a,int b){return a>b?a:b;} template <class T>void read(T &x) { x=0;int f=0; char ch=getchar(); while(ch<'0'||ch>'9'){f=(ch=='-');ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} x=f?-x:x; } int gf(int x){return x==f[x]?x:gf(f[x]);} int gf_d(int x) { int d; for(d=1;x!=f[x];x=f[x])d++; return d; } void swap(int &a,int &b){if(a==b)return;a^=b;b^=a;a^=b;} void union_fa(int x,int y,int d) { x=gf(x),y=gf(y); if(x==y)return; if(rank[x]>rank[y]) swap(x,y); if(rank[x]==rank[y]) rank[y]++; f[x]=y; cost[x]=d; } void BFS() { h=0; while(h<l) { now=q[++h]; for(int d=0;d<4;d++) if(O[now.x+dx[d]][now.y+dy[d]]) { if(last[now.x+dx[d]][now.y+dy[d]]&&last[now.x+dx[d]][now.y+dy[d]]!=last[now.x][now.y]) bot[++top]=(Node){last[now.x+dx[d]][now.y+dy[d]],last[now.x][now.y],dis[now.x+dx[d]][now.y+dy[d]]+dis[now.x][now.y]}; else if(!dis[now.x+dx[d]][now.y+dy[d]]) { dis[now.x+dx[d]][now.y+dy[d]]=dis[now.x][now.y]+1; last[now.x+dx[d]][now.y+dy[d]]=last[now.x][now.y]; q[++l]=(Palce){now.x+dx[d],now.y+dy[d]}; } } } } int jump(int x,int y) { int d=0; if(depth[x]<depth[y])swap(x,y); for(;depth[x]!=depth[y];)d=max(d,cost[x]),x=f[x]; for(;x!=y;)d=max(d,cost[x]),d=max(d,cost[y]),x=f[x],y=f[y]; return d; } int main() { // freopen("C.in","r",stdin); // freopen("C.out","w",stdout); read(H),read(W); read(P),read(Q); for(int i=1;i<=H;i++) { scanf("%s",ch+1); for(int j=1;j<=W;j++)O[i][j]=(ch[j]!='#'); } for(int i=1;i<=P;i++) { read(x),read(y); f[i]=i; rank[i]=1; last[x][y]=i; q[++l]=(Palce){x,y}; } BFS();//O(H*W) std::sort(bot+1,bot+1+top,cmp); for(int i=1;i<=top;i++) union_fa(bot[i].a,bot[i].b,bot[i].c);//按kruskal走,那么两个连通块之间的过往最大值就是这条边,所以可以直接在并查集的树上求最值。 for(int i=1;i<=P;i++)depth[i]=gf_d(i);//O(PlogP) for(int i=1;i<=Q;i++) { read(x);read(y); if(gf(x)!=gf(y))printf("-1\n"); else printf("%d\n",jump(x,y));//O(logP) } return 0; }