• 【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-kleq |a_i-a_j|leq d+k)
    给定 (a_1,...,a_n)(d)。你需要回答 (q) 次询问,每次询问给定一个灵活程度 (k) 和一个下标 (i),你需要回答:此时的小青蛙能否跳到 (a_i)
    (1leq n,qleq 2 imes 10^5)(1leq s,ileq n)(1leq a_i,d,kleq 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(nlog n)) 的。时间复杂度 (O(nlog^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
    ";
    			else cout<<"No
    ";
    	}
    	return 0;	
    }
    
  • 相关阅读:
    iOS开发-类簇(Class Cluster)
    算法-有向图及可达性
    算法-无向图(连通分量,是否有环和二分图)
    算法-无向图(深度优先搜索和广度优先搜索)
    算法-无向图
    算法-散列表
    Red-Gate.NET.Reflector.v8.0.1.308(内含注册机Keygen与注册图解)
    [转]c#快捷键
    Windows常用性能计数器总结
    [转]C#程序性能优化
  • 原文地址:https://www.cnblogs.com/stoorz/p/15241611.html
Copyright © 2020-2023  润新知