【题目】
原题地址
给定一个二维平面上的四连通块,相邻格子间距离为 1 1 1,有两种操作:
所有数字 ≤ 3 × 1 0 5 \leq 3\times 10^5 ≤3×105
【解题思路】
这道题的难点就在于一步很巧妙的转化:对于每一列,将每个连通块看作一个点,向它左右相邻的连通块连边。
这样可以发现我们得到了一棵树,两点之间的路径可以在树上唯一表示出来。注意到两点之间的路径一定在某一列的一块停止,而且进入时一定走的是最短距离。我们假设当前两个点为 a a a和 b b b,相遇的块为 C C C,那么答案就是 a a a到 C C C的距离加上 b b b到 C C C的距离(这里距离是指到块内最近的一个点),再加上进入时两点纵坐标的差的绝对值,即 d a + d b + ∣ y a − y b ∣ d_a+d_b+|y_a-y_b| da+db+∣ya−yb∣。
一棵树?支持查询离一个点最近的标记点,将一个点打标记,我们可以直接上点分树。更具体地说,我们构点分树时,维护每个格子到它的点分树上的祖先们块的最近距离以及对应的格子,每个格子再维护它所属块内距离它最近的商店。观察到式子后面那个绝对值实际上就是一个前缀最小值,维护一个 BIT \text{BIT} BIT即可。
复杂度 O ( ( n + q ) log n ) O((n+q)\log n) O((n+q)logn)
写起来十分麻烦,细节还是需要注意一下的。
【参考代码】
#include
#define pb push_back
using namespace std;
const int N=3e5+10,INF=1e8+10;
int n,m,Q,cnt,no;
int b[N],c[N],lx[N],ly[N];
int read()
{
int ret=0;char c=getchar();
while(!isdigit(c)) c=getchar();
while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
return ret;
}
void gmin(int &x,int y){x=min(x,y);}
namespace BIT
{
#define lowbit(x) (x&(-x))
vector<int>bit1[N],bit2[N];
void update1(int k,int x,int v){for(;x<=c[k];x+=lowbit(x))gmin(bit1[k][x],v);}
int query1(int k,int x){int res=INF;for(;x;x-=lowbit(x))gmin(res,bit1[k][x]);return res;}
void update2(int k,int x,int v){for(;x;x-=lowbit(x))gmin(bit2[k][x],v);}
int query2(int k,int x){int res=INF;for(;x<=c[k];x+=lowbit(x))gmin(res,bit2[k][x]);return res;}
}
using namespace BIT;
namespace Dynamic
{
int sum,rt;
int siz[N],son[N],dep[N],fa[N],vis[N];
int p[N][22],dis[N][22];
vector<int>g[N],e[N],s[N];
map<int,int>id[N];
queue<int>q;
void addg(int x,int y){g[x].pb(y);g[y].pb(x);}
void adde(int x,int y){e[x].pb(y);e[y].pb(x);}
void init()
{
for(int x=1;x<=m;++x)
{
if(s[x].empty()) continue;
sort(s[x].begin(),s[x].end());
int j=0;
for(int i=0;i<(int)s[x].size();++i)
{
int v=s[x][i];id[x][v]=++no;
if(i && s[x][i-1]+1==v) addg(no-1,no); else ++cnt,lx[cnt]=x,ly[cnt]=i;
b[no]=cnt;c[cnt]++;
while(j<(int)s[x-1].size() && s[x-1][j]<v) ++j;
if(j<(int)s[x-1].size() && s[x-1][j]==v)
{
int t=id[x-1][s[x-1][j]];
addg(no,t);adde(cnt,b[t]);
}
}
}
for(int i=1;i<=cnt;++i)
{
sort(e[i].begin(),e[i].end());
e[i].erase(unique(e[i].begin(),e[i].end()),e[i].end());
}
for(int i=1;i<=cnt;++i) for(int j=0;j<=c[i];++j) bit1[i].pb(INF),bit2[i].pb(INF);
}
void getroot(int x,int f)
{
siz[x]=c[x];son[x]=0;
for(int i=0;i<(int)e[x].size();++i)
{
int v=e[x][i];
if(vis[v] || v==f) continue;
getroot(v,x);siz[x]+=siz[v];son[x]=max(son[x],siz[v]);
}
son[x]=max(son[x],sum-siz[x]);
if(son[x]<son[rt]) rt=x;
}
void build(int x,int d)
{
while(!q.empty()) q.pop();
for(int i=ly[x];i<ly[x]+c[x];++i)
{
int y=s[lx[x]][i],u=id[lx[x]][y];
p[u][d]=i-ly[x]+1;dis[u][d]=0;q.push(u);
}
while(!q.empty())
{
int u=q.front();q.pop();
for(int i=0;i<(int)g[u].size();++i)
{
int v=g[u][i];
if(vis[b[v]] || p[v][d]) continue;
p[v][d]=p[u][d];dis[v][d]=dis[u][d]+1;q.push(v);
}
}
}
void solve(int x,int d)
{
vis[x]=1;dep[x]=d;build(x,d);
for(int i=0;i<(int)e[x].size();++i)
{
int v=e[x][i];
if(vis[v]) continue;
rt=0;sum=siz[v];getroot(v,x);fa[rt]=x;solve(rt,d+1);
}
}
void update(int x)
{
for(int y=b[x],d;y;y=fa[y])
{
d=dep[y],update1(y,p[x][d],dis[x][d]-p[x][d]),update2(y,p[x][d],dis[x][d]+p[x][d]);
//printf("update:%d %d %d %d %d %d\n",c[y],y,x,d,p[x][d],dis[x][d]);
}
}
int query(int x)
{
int res=INF;
for(int y=b[x],d;y;y=fa[y])
{
d=dep[y],gmin(res,dis[x][d]+p[x][d]+query1(y,p[x][d])),gmin(res,dis[x][d]-p[x][d]+query2(y,p[x][d]));
//printf("query:%d %d %d %d %d %d\n",c[y],y,x,d,p[x][d],dis[x][d]);
}
return res<N?res:-1;
}
void solution()
{
n=read();
for(int i=1;i<=n;++i){int x=read(),y=read();m=max(m,x);s[x].pb(y);}
init();son[0]=sum=n;getroot(1,0);solve(rt,0);
Q=read();
while(Q--)
{
int op=read(),x=read(),y=read();
if(op&1) update(id[x][y]); else printf("%d\n",query(id[x][y]));
}
}
};
int main()
{
#ifndef ONLINE_JUDGE
freopen("CF936E.in","r",stdin);
freopen("CF936E.out","w",stdout);
#endif
Dynamic::solution();
return 0;
}
【总结】
这是一道十分巧妙的题目,拓展了思维的同时让我已近干涸的码力得到了些许的滋润。
现在CF上跑的最快的代码2333