BZOJ4242 水壶
第一篇啦,来点猛的
可能只是我太弱了
awsl,打完这道题我再起不能
题目
JOI君所居住的IOI市以一年四季都十分炎热著称。
IOI市是一个被分成纵H*横W块区域的长方形,每个区域都是建筑物、原野、墙壁之一。建筑物的区域有P个,编号为1…P。
JOI君只能进入建筑物与原野,而且每次只能走到相邻的区域中,且不能移动到市外。
JOI君因为各种各样的事情,必须在各个建筑物之间往返。虽然建筑物中的冷气设备非常好,但原野上的日光十分强烈,因此在原野上每走过一个区域都需要1单位的水。此外,原野上没有诸如自动售货机、饮水处之类的东西,因此IOI市的市民一般都携带水壶出行。大小为x的水壶最多可以装x单位的水,建筑物里有自来水可以将水壶装满。
由于携带大水壶是一件很困难的事情,因此JOI君决定携带尽量小的水壶移动。因此,为了随时能在建筑物之间移动,请你帮他写一个程序来计算最少需要多大的水壶。
现在给出IOI市的地图和Q个询问,第i个询问(1<=i<=Q)为“在建筑物Si和Ti之间移动,最小需要多大的水壶?”,请你对于每个询问输出对应的答案。
数据范围(wdnmd!):
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)
心路历程(雾)&&解题思路
一开始以为是多源最短路,想到了floyd,可是P<=2*10^5让我太难了。其实这道题由于每到一个城市都可以补充水杯,所以不必考虑一个人会走多远,你甚至可以让JOJOJOI君绕很大一个圈子挨着房子走,比如说:
(我用数字表示建筑物编号)
123456
.####7
.####8
.####9
....10.
这样从1号建筑走到10号要求的最小水壶大小就是1
那么怎么求两两建筑的嘴短距离呢
还记得bfs么,因为bfs搜到的第一个点一定是最优的(路长,时间等),又有一个神奇的东西叫多向bfs,从多个起点开始出发搜索,每次取出队首元素对应点,加上增量数组,算出能去到的点,如果能去到的点点已经被访问,那么你就找到了当前点的起始城市和能去到的那个点的起始城市之间的最短路径(因为两端都是bfs,找到的点一定是最短路径上的点)。这样,我们就找出了两两城市的“路径”。
可是,这是一个无向图,处理起来又慢又麻烦。而前面已经说过,不必考虑路径的长度,所以我们可以考虑Kruskal重构树(最小生成树),这样就能使两个建筑之间的最长边最短。
我的代码比较玄学,在Kruskal连边的时候要连当前边的两个节点的并查集中的父亲节点的双向边。可以证明这道题中这是正确的:因为Kruskal会使当前一个已构成的集合是一棵树,而对于任意一个集合的并查集中的总父亲(getfather(x))一定是这个集合对应的树的一个root节点。对于位于两颗树的建筑x,y,当我们要对他们连边时,可以对两颗树的root节点连边,因为根据Kruskal,当前的树中所有边都比x和y之间的边小,而这样连边会使从一棵树的x走到另一棵树的y时一定会走过我们刚刚连的边,能保证我们答案的正确性
可是如果是连x和y之间的边而不是getfather(x)和gefather(y)之间的边会WA90
接下来,对于一棵树我们就可以通过倍增来求树上一个点与另一个点的最近公共祖先,顺便可以把x->LCA(x,y)->y路径上的最大边权求出来(再开一个倍增数组来实现)
上代码:
#include<stdio.h>
#include<bits/stdc++.h>
#define ll long long
#define intinf 1e9
#define llinf 1e17
using namespace std;
const int maxn=2e5+100;
const int N=2010;
int dep[maxn],fa[maxn][20],ma[maxn][20];//倍增的各个数组
int father[maxn];//并查集
int Next[N*N*2],Last[N*N*2],End[N*N*2],Len[N*N*2],cnt;//链式前向星存重构树
int n,m,p,question;
int d[4][2]={{1,0},{-1,0},{0,1},{0,-1}};//bfs--增量数组
char Map[N][N];//存初始矩阵
int root[N][N],dii[N][N];//bfs--root[i][j]表示(i,j)的起始城市,dii[i][j]表示从root[i][j]到(i,j)的距离
struct data{
int x,y;
};
struct edge{
int x,y,len;
bool operator < (const edge &a) const{
return len<a.len;
}
}E[N*N*2];
int tot;
void add(int x,int y,int z)
{
End[++cnt]=y;
Len[cnt]=z;
Next[cnt]=Last[x];
Last[x]=cnt;
}
queue<data>q;
int getfather(int v)
{
if(father[v]!=v) father[v]=getfather(father[v]);
return father[v];
}
bool visit[2010][2010];//广搜标记数组
void bfs()
{
data tmp,wdnmd;int sx,sy,dx,dy,di;
while(q.size())
{
tmp=q.front();q.pop();
sx=tmp.x;sy=tmp.y;
for(int i=0;i<4;i++)
{
dx=sx+d[i][0];dy=sy+d[i][1];
if(dx>=1&&dy>=1&&dx<=n&&dy<=m&&Map[dx][dy]=='.')
{
if(root[dx][dy]!=0&&root[dx][dy]!=root[sx][sy])
{
tot++;
E[tot].x=root[sx][sy];
E[tot].y=root[dx][dy];
E[tot].len=dii[dx][dy]+dii[sx][sy];
}
else if(visit[dx][dy]==0)
{
visit[dx][dy]=1;
wdnmd.x=dx;wdnmd.y=dy;
root[dx][dy]=root[sx][sy];
dii[dx][dy]=dii[sx][sy]+1;
q.push(wdnmd);
}
}
}
}
}
bitset<maxn>awsl;//深搜标记数组
void dfs(int x)
{
int i,k,y;
awsl[x]=1;
dep[x]=dep[fa[x][0]]+1;
k=ceil(log2(dep[x]));
for(i=1;i<=k;i++)
{
ma[x][i]=max(ma[x][i-1],ma[fa[x][i-1]][i-1]);
fa[x][i]=fa[fa[x][i-1]][i-1];
}
for(i=Last[x];i;i=Next[i])
{
if(End[i]!=fa[x][0]&&awsl[End[i]]==0)
{
int v=End[i];
fa[v][0]=x;ma[v][0]=Len[i];
dep[v]=dep[x]+1;
dfs(v);
}
}
}
int LCA(int x,int y)
{
int i,k,s,d=0;
s=ceil(log2(n));
if(dep[x]<dep[y]) swap(x,y);
k=dep[x]-dep[y];
for(i=0;i<=s;i++)
{
if(k&(1<<i))
d=max(d,ma[x][i]),x=fa[x][i];
}
if(x==y) return d;
s=ceil(log2(dep[x]));
for(i=s;i>=0;i--)
if(fa[x][i]!=fa[y][i])
{
d=max(d,max(ma[x][i],ma[y][i]));x=fa[x][i];y=fa[y][i];
}
if(x!=y) d=max(d,max(ma[x][0],ma[y][0]));
return d;
}
int main()
{
scanf("%d %d %d %d",&n,&m,&p,&question);
for(register int i=1;i<=n;i++) scanf("%s",Map[i]+1);
int x,y;data tmp;
for(int i=1;i<=p;i++)
{
father[i]=i;
scanf("%d %d",&x,&y);
tmp.x=x;tmp.y=y;
q.push(tmp);
visit[x][y]=1;
root[x][y]=i;dii[x][y]=0;
}
bfs();//bfs找边
sort(E+1,E+tot+1);
int fx,fy,k=0,sum=0,le;
while(k<tot&&sum<p-1)//Kruskal重构最小生成树
{
k++;x=E[k].x;y=E[k].y;le=E[k].len;
fx=getfather(x);fy=getfather(y);
if(fx!=fy)
{
sum++;
father[fx]=fy;
add(fx,fy,le);add(fy,fx,le);
}
}
for(int i=1;i<=p;i++)//dfs初始化倍增
{
if(getfather(i)==i&&awsl[i]==0)
{
dep[i]=1;
dfs(i);
}
}
while(question--)
{
scanf("%d %d",&x,&y);
if(getfather(x)!=getfather(y)) printf("-1\n");
else printf("%d\n",LCA(x,y));
}
return 0;
}
自认为码风很好
这题码量较大,编码注意细节
注意!!! 此题开fileread会卡掉OJ评测机!!
这是比较毒瘤的题了,考了bfs+dfs+Kruskal+并查集+倍增/LCA,写完这题的我再起不能
最后祝大家AC愉快