【CF1550F】Jumping Around

题目

题目链接:https://codeforces.com/contest/1550/problem/F
数轴上顺次有 \(n\) 个点 \(a_1 < a_2 < \cdots < a_n\)
有一只小青蛙,初始时在 \(a_s\) 处。小青蛙有两个参数:步长 \(d\) 和灵活程度 \(k\)。其中,步长 \(d\) 是确定的,而灵活程度 \(k\) 是可以调整的。
小青蛙可以从某个点跳到另一个点。但这是有要求的:小青蛙能从 \(a_i\) 跳到 \(a_j\),当且仅当 \(d-k\leq |a_i-a_j|\leq d+k\)
给定 \(a_1,...,a_n\)\(d\)。你需要回答 \(q\) 次询问,每次询问给定一个灵活程度 \(k\) 和一个下标 \(i\),你需要回答:此时的小青蛙能否跳到 \(a_i\)
\(1\leq n,q\leq 2\times 10^5\)\(1\leq s,i\leq n\)\(1\leq a_i,d,k\leq 10^6\)

思路

有一个显然的做法:对于 \(i,j\) 两点,在他们之间连权值为 \(||a_i-a_j|-d|\) 的边,然后跑最小生成树。那么一个点 \(i\) 最小需要的 \(k\) 就是最小生成树上 \(s\)\(i\) 的边中权值的最大值。
但是这样边数是 \(O(n^2)\) 的。prim 和 kruskal 都不好搞。考虑一度被认定没用的 boruvka 算法。
boruvka 算法的流程如下:

  1. 一开始所有点各自为一个连通块。
  2. 对于每一个连通块,找到另一端不在该连通块的最小边权的边。
  3. 连接所有找到的边。
  4. 如果此时所有点连通则退出。否则回到步骤 \(2\)

正确性比较显然,而如果此时有 \(k\) 个连通块,就至少会合并 \(\frac{k}{2}\) 个连通块,所以时间复杂度为 \(O((n+m)\log n)\)
发现这个过程中复杂度与边权有关的地方只有步骤 \(2\) 中需要枚举所有的边。但是这道题中的边权是有性质的。
维护一个 set,把所有 \(a_i\) 扔进去。步骤 \(2\) 中,对于一个连通块,我们枚举连通块内的所有点,把他们从 set 中删除。然后再次枚举连通块的所有点 \(i\),为了找到边权最小的边,其实就是需要在 set 中找到距离 \(a_i+d\)\(a_i-d\) 最近的点。因为已经把连通块内的点在 set 中删除了,所以可以直接在 set 上二分 \(4\) 次找到。最后再把所有点加回来即可。
这样每进行一次步骤 \(2\),我们找的边数是 \(O(n\log n)\) 的。时间复杂度 \(O(n\log^2 n)\)

代码

#include <bits/stdc++.h>
using namespace std;

const int N=200010,M=1000010,Inf=1e9;
int n,m,Q,rt,d,tot,head[N],a[N],ans[N],val[N],father[N],id[M];
set<int> s;
vector<int> b[N];

struct edge
{
	int next,to,dis;
}e[N*2];

struct edge1
{
	int u,v,dis;
}e1[N];

int find(int x)
{
	return x==father[x]?x:father[x]=find(father[x]);
}

int getd(int x,int y)
{
	return abs(abs(x-y)-d);
}

void check(int &u,int &v,int x,int y)
{
	if (y==Inf || y==-Inf) return;
	if (getd(x,y)<getd(u,v)) u=x,v=y;
}

void add(int from,int to,int dis)
{
	e[++tot]=(edge){head[from],to,dis};
	head[from]=tot;
}

void boruvka()
{
	m=n;
	for (int k=1;k<n;)
	{
		for (int i=1;i<=n;i++) b[i].clear();
		for (int i=1;i<=n;i++) b[find(i)].push_back(i);
		int cnt=0;
		for (int i=1;i<=n;i++)
			if (find(i)==i)
			{
				int u=0,v=Inf;
				for (int j=0;j<b[i].size();j++)
					s.erase(s.find(a[b[i][j]]));
				for (int j=0;j<b[i].size();j++)
				{
					check(u,v,a[b[i][j]],*s.lower_bound(a[b[i][j]]+d));
					check(u,v,a[b[i][j]],*(--s.lower_bound(a[b[i][j]]+d)));
					check(u,v,a[b[i][j]],*s.lower_bound(a[b[i][j]]-d));
					check(u,v,a[b[i][j]],*(--s.lower_bound(a[b[i][j]]-d)));
				}
				if (u) e1[++cnt]=(edge1){id[u],id[v],getd(u,v)};
				for (int j=0;j<b[i].size();j++)
					s.insert(a[b[i][j]]);
			}
		for (int i=1;i<=cnt;i++)
			if (find(e1[i].u)!=find(e1[i].v))
			{
				k++;
				int u=e1[i].u,v=e1[i].v,dis=e1[i].dis;
				add(u,v,dis); add(v,u,dis);
				father[find(u)]=find(v);
			}
	}
}

void dfs(int x,int fa,int mx)
{
	ans[x]=mx;
	for (int i=head[x];~i;i=e[i].next)
	{
		int v=e[i].to;
		if (v!=fa) dfs(v,x,max(mx,e[i].dis));
	}
}

int main()
{
	memset(head,-1,sizeof(head));
	scanf("%d%d%d%d",&n,&Q,&rt,&d);
	for (int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		s.insert(a[i]); b[i].push_back(i);
		father[i]=i; id[a[i]]=i;
	}
	s.insert(-Inf); s.insert(Inf);
	boruvka();
	dfs(rt,0,0);
	while (Q--)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		if (ans[x]<=y) cout<<"Yes\n";
			else cout<<"No\n";
	}
	return 0;	
}
posted @ 2021-09-08 10:31  stoorz  阅读(53)  评论(0编辑  收藏  举报