【BZOJ 4242】水壶

在炎热的IOI市,JOI君需在建筑物间移动,原野上每过一区需消耗1单位水。任务是计算在两建筑间移动所需的最小水壶容量,涉及网格图最小生成树及LCA算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

JOI 君所居住的 IOI 市以一年四季都十分炎热著称。

IOI 市是一个被分成 纵H×横W纵H\times 横WH×W 块区域的长方形,每个区域都是建筑物、原野、墙壁之一。建筑物的区域有 PPP 个,编号为 1…P1\dots P1P

JOI 君只能进入建筑物与原野,而且每次只能走到相邻的区域中,且不能移动到市外。

JOI 君因为各种各样的事情,必须在各个建筑物之间往返。虽然建筑物中的冷气设备非常好,但原野上的日光十分强烈,因此在原野上每走过一个区域都需要 111 单位的水。此外,原野上没有诸如自动售货机、饮水处之类的东西,因此 IOI 市的市民一般都携带水壶出行。大小为 xxx 的水壶最多可以装 xxx 单位的水,建筑物里有自来水可以将水壶装满。

由于携带大水壶是一件很困难的事情,因此 JOI 君决定携带尽量小的水壶移动。因此,为了随时能在建筑物之间移动,请你帮他写一个程序来计算最少需要多大的水壶。

现在给出 IOI 市的地图和 QQQ 个询问,第 i(1≤i≤Q)i(1\le i\le Q)i(1iQ) 个询问为“在建筑物 SiS_iSiTiT_iTi 之间移动,最小需要多大的水壶?”,请你对于每个询问输出对应的答案。

1≤H≤20001\le H\le 20001H20001≤W≤20001\le W\le 20001W20002≤P≤2×1052\le P\le 2\times 10^52P2×1051≤Q≤2×1051\le Q\le 2\times 10^51Q2×1051≤Ai≤H(1≤i≤P)1\le A_i\le H(1\le i\le P)1AiH(1iP)1≤Bi≤W(1≤i≤P)1\le B_i\le W(1\le i\le P)1BiW(1iP)(Ai,Bi)≠(Aj,Bj)(1≤i&lt;j≤P)(A_i,B_i)≠(A_j,B_j)(1\le i\lt j\le P)(Ai,Bi)̸=(Aj,Bj)(1i<jP)1≤Si&lt;Ti≤P(1≤i≤Q)1\le S_i\lt T_i\le P(1\le i\le Q)1Si<TiP(1iQ)

算法分析

大神题。

由于我们可能会先到达一个建筑物将水壶装满再前往终点,因此所求就是路上经过的两个建筑物之间最长距离的最小值,其实就是 【NOIP 2013】货车运输。

只是直接建图的话边数较多,这里用一种神奇的 BFS 方法来建出网格图的最小生成树。

从每个节点开始宽度优先搜索,对每个格子记录其第一次被访问到的时间(同时也是与它距离最近的节点的距离)和与它距离最近的节点编号,搜索时遇到一个已经被遍历到的节点就将与它距离最近的节点和出发节点连一条边。

这样,得到的边中就包含了最小生成树中的所有边,需要跑完整张图以使所有能够连通的节点连通。

为了提高时间效率,我们对每个边长建一个桶来保存边长为该值的边以省略排序操作。

然后是 【NOIP 2013】货车运输 问题的 Kruskal重构树 解法,根据其堆性质,两点间路径边权的最小值就是其重构树中的最近公共祖先的权值,倍增求一遍 LCA 即可。

注意,由于不一定连通,求深度时要从大到小对每个点都 DFS 一遍。

代码实现

#include <cstdio>
#include <queue>
#include <utility>
const int maxn=2005;
const int maxp=(int)2e5+5;;
typedef std::pair<int,int> P;
const int arr[4][2]={{0,1},{1,0},{0,-1},{-1,0}};
char s[maxn][maxn];int d[maxn][maxn],vis[maxn][maxn];
struct pos {int x,y;} ps[maxp];
std::vector<P> edges[maxn*maxn];
int head[maxp<<1],ev[maxp<<1],nxt[maxp<<1],idx=0;
inline void add(int u,int v) {ev[++idx]=v;nxt[idx]=head[u];head[u]=idx;}
int fa[maxp<<1];int find(int x) {return x==fa[x]?x:fa[x]=find(fa[x]);}
int pa[maxp<<1][22],dep[maxp<<1],len[maxp<<1];
inline void dfs(int x) {
	for(register int i=head[x];i;i=nxt[i]) {dep[ev[i]]=dep[x]+1;dfs(ev[i]);}
}
inline int LCA(int x,int y) {
	if(dep[x]<dep[y]) std::swap(x,y);
	for(int i=21;i>=0;--i) if((dep[x]-dep[y])>>i&1) x=pa[x][i];
	if(x==y) return x;
	for(int i=21;i>=0;--i) if(pa[x][i]^pa[y][i]) {x=pa[x][i];y=pa[y][i];}
	return pa[x][0];
}
int main() {
	int h,w,p,q;scanf("%d%d%d%d",&h,&w,&p,&q);
	for(int i=1;i<=h;++i) scanf("%s",s[i]+1);
	std::queue<pos> que;
	for(register int i=1;i<=p;++i) {
		scanf("%d%d",&ps[i].x,&ps[i].y);que.push(ps[i]);
		d[ps[i].x][ps[i].y]=0;vis[ps[i].x][ps[i].y]=i;
	}
	while(que.size()) {
		pos cur=que.front();que.pop();int x=cur.x,y=cur.y;
		for(register int i=0;i<4;++i) {
			int nx=x+arr[i][0],ny=y+arr[i][1];
			if(1<=nx&&nx<=h&&1<=ny&&ny<=w&&s[nx][ny]!='#'&&vis[nx][ny]!=vis[x][y]) {
				if(vis[nx][ny]) edges[d[nx][ny]+d[x][y]].push_back(P(vis[x][y],vis[nx][ny]));
				else {que.push((pos){nx,ny});d[nx][ny]=d[x][y]+1;vis[nx][ny]=vis[x][y];}
			}
		}
	}
	for(register int i=1;i<=(p<<1);++i) fa[i]=i;
	for(register int i=0;i<=h*w;++i) {
		for(register int j=0,end=edges[i].size();j<end;++j) {
			int u=edges[i][j].first,v=edges[i][j].second;
			int x=find(u),y=find(v);if(x==y) continue;
			pa[x][0]=pa[y][0]=fa[x]=fa[y]=++p;len[p]=i;add(p,x);add(p,y);
		}
	}
	for(int i=p;i>=1;--i) if(!dep[i]) {dep[i]=1;dfs(i);}
	for(register int i=1;i<=21;++i) for(register int x=1;x<=p;++x) pa[x][i]=pa[pa[x][i-1]][i-1];
	while(q--) {
		int s,t;scanf("%d%d",&s,&t);
		printf("%d\n",(find(s)^find(t))?-1:len[LCA(s,t)]);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值