【点分治+BIT】CF936E Iqea

【题目】
原题地址
给定一个二维平面上的四连通块,相邻格子间距离为 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+yayb

一棵树?支持查询离一个点最近的标记点,将一个点打标记,我们可以直接上点分树。更具体地说,我们构点分树时,维护每个格子到它的点分树上的祖先们块的最近距离以及对应的格子,每个格子再维护它所属块内距离它最近的商店。观察到式子后面那个绝对值实际上就是一个前缀最小值,维护一个 BIT \text{BIT} BIT即可。
复杂度 O ( ( n + q ) log ⁡ n ) O((n+q)\log n) O((n+q)logn)

写起来十分麻烦,细节还是需要注意一下的。

【参考代码】

#include<bits/stdc++.h>
#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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值