Solution 水筒
题目大意:一个\(n \times m\)的矩阵里有一些墙不能走,有一些建筑物,有一些地方是原野。每走过一格原野需要消耗一份水,建筑物可以将水壶补满。多次询问从一个建筑物到另一个建筑物水壶容量至少为多少。
Kruskal重构树
分析:
首先不难想到暴力,我们把所有可以相互到达的建筑物两两连边,如果我们要使得最大边权尽量小那么我们就要求最小生成树。证明考虑贪心:不断加边直到两点连通,这就是Kruskal的过程。
求树上两点间边权的最大值就是Kruskal重构树的板子题
但是如果我们暴力枚举所有建筑物无法承受,但是我们发现有些边是没有用的,我们可以考虑类似于缩点的方法。我们给每个建筑物一个颜色,然后同一颜色的点离它最近的加油站都相同
这样我们只需要枚举每个点以及和它相邻的点,如果颜色不同连边即可,这个可以在bfs的时候顺便计算,染色发现已经染过的不同色点就连边
#include <cstdio> #include <vector> #include <queue> #include <utility> #include <algorithm> using namespace std; const int maxn = 2e3 + 100,maxp = 4e5 + 100,maxdep = 25; int n,m,p,q,vis[maxn][maxn],dis[maxn][maxn],deltax[] = {-1,0,1,0},deltay[] = {0,-1,0,1}; namespace Graph{ struct Edge{int from,to,dist;}; vector<Edge> G[maxp]; inline void addedge(int from,int to,int dist){G[from].push_back(Edge{from,to,dist});} int faz[maxp][maxdep + 1],dep[maxp],vis[maxp],col[maxp],val[maxp],col_tot; inline void init(int u){ vis[u] = 1;col[u] = col_tot; for(int i = 1;i <= maxdep;i++) faz[u][i] = faz[faz[u][i - 1]][i - 1]; for(auto e : G[u]){ if(e.to == faz[u][0])continue; faz[e.to][0] = u; dep[e.to] = dep[u] + 1; init(e.to); } } inline void init(){ for(int i = (p << 1) - 1;i >= 1;i--) if(!vis[i])dep[i] = 1,col_tot++,init(i); } inline int lca(int x,int y){ if(dep[x] < dep[y])swap(x,y); for(int i = maxdep;i >= 0;i--) if(dep[faz[x][i]] >= dep[y]) x = faz[x][i]; if(x == y)return x; for(int i = maxdep;i >= 0;i--) if(faz[x][i] != faz[y][i]) x = faz[x][i],y = faz[y][i]; return faz[x][0]; } inline int query(int x,int y){return col[x] != col[y] ? -1 : val[lca(x,y)];} } namespace Kruskal{ using Graph::Edge; vector<Edge> Edges; inline void addedge(int from,int to,int dist){Edges.push_back(Edge{from,to,dist});} int f[maxp],sz; inline void init(){for(int i = 1;i <= p;i++)f[i] = i;sz = p;} inline int find(int x){return x == f[x] ? x : f[x] = find(f[x]);} inline void kruskal(){ sort(Edges.begin(),Edges.end(),[](const Edge &a,const Edge &b){return a.dist < b.dist;}); init(); int tot = p,lim = (p << 1) - 1; for(int i = 1;i <= lim;i++)f[i] = i; for(auto e : Edges){ int fx = find(e.from),fy = find(e.to); if(fx != fy){ f[fx] = f[fy] = ++tot; Graph::val[tot] = e.dist; Graph::addedge(tot,fx,0),Graph::addedge(tot,fy,0); if(tot == lim)break; } } } } inline bool check(int x,int y){return x >= 1 && x <= n && y >= 1 && y <= m;} char mp[maxn][maxn]; struct Pos{int x,y;}building[maxp]; inline void bfs(){ queue<Pos> Q; for(int i = 1;i <= p;i++) Q.push(building[i]),vis[building[i].x][building[i].y] = i; while(!Q.empty()){ Pos now = Q.front();Q.pop(); int x = now.x,y = now.y; for(int i = 0;i < 4;i++){ int nx = x + deltax[i],ny = y + deltay[i]; if(!check(nx,ny) || mp[nx][ny] == '#' || vis[nx][ny] == vis[x][y])continue; if(vis[nx][ny] && vis[x][y] != vis[nx][ny]){Kruskal::addedge(vis[x][y],vis[nx][ny],dis[x][y] + dis[nx][ny]);continue;} vis[nx][ny] = vis[x][y]; dis[nx][ny] = dis[x][y] + 1; Q.push(Pos{nx,ny}); } } } int main(){ scanf("%d %d %d %d",&n,&m,&p,&q); for(int i = 1;i <= n;i++)scanf("%s",mp[i] + 1); for(int i = 1;i <= p;i++)scanf("%d %d",&building[i].x,&building[i].y); bfs(); Kruskal::kruskal(); Graph::init(); for(int u,v,i = 1;i <= q;i++) scanf("%d %d",&u,&v),printf("%d\n",Graph::query(u,v)); return 0; }