HT-014 Div3 跳棋 题解 [ 黄 ] [ 并查集 ] [ 链表 ]

分析

依旧是一个连通块题。

观察题面不难发现两个重要性质:

  1. 一个跳棋只能以它旁边的两个跳棋为中点跳跃,且满足跳跃路线中 除中点以外没有其它跳棋阻挡。
  2. 只有我们的跳棋可以移动。
  3. 跳棋的操作具有可逆性/对称性。

第三条性质可以这么理解,就是一个跳棋跳过去之后,它还可以跳回来。
因此,无论我们从左右两边的哪一边开始起跳,都可以跳出最佳路径。

于是我们规定跳棋只能向右跳。(你规定向左跳也可以)

然后发现,跳棋跳的路线,是可以抽象成一条链的。
每次从 u 点跳向 v 点,可以看作两者连一条边。
这样链就建好了。

但这样仍然会超时,因为每次我们从链的某个节点进入,走到最后进行查询操作,每个格子进行一遍,就相当于是 O(n2) 的复杂度了。

并且还有一个性质,就是我们只注重跳到的最后的结果,而链表又相当于是一种特殊的树。
所以我们想到既可以实现 O(1) 快速查询,又可以维护儿子与祖先关系的并查集
简化树的操作,便是路径压缩

由于是连通块,所以使用 BFS 看似也可以实现,但我没试过,并且感觉有点难写。或者写遍历树的做法也可以,稍微比 BFS 好写些。实现的好那么上面三种做法都没问题。

时间为 O(n)

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m,f[200005],l[200005],r[200005],jp[200005],fr[200005],ans=0;
bool blk[200005];
void init()
{
	for(int i=0;i<200005;i++)
	{
		f[i]=i;
		l[i]=i;
		r[i]=i;
	}
}
int findf(int x)
{
	if(f[x]!=x)f[x]=findf(f[x]);
	return f[x];
}
void combine(int x,int y)
{
	int fx=findf(x),fy=findf(y);
	f[fx]=fy;
	l[fy]=min(l[fy],l[fx]);
	r[fy]=max(r[fy],r[fx]);
}
int main()
{
	freopen("jump.in","r",stdin);
	freopen("jump.out","w",stdout);
	cin>>n>>m;
	init();
	for(int i=1;i<=m;i++)
	{
		int a;
		cin>>a;
		blk[a]=1;
	}
	for(int i=1;i<=n;i++)
	{
		fr[i]=fr[i-1];
		fr[i]+=blk[i];
	}
	for(int i=n,b=0x3f3f3f3f;i>=1;i--)
	{
		if(blk[i])
		{
			jp[i]=0x3f3f3f3f;
			b=i;
			continue;
		}
		jp[i]=b;
	}
	for(int i=1;i<=n;i++)
	{
		if(jp[i]>=0x3f3f3f3f)continue;
		int p=2*jp[i]-i;
		if(p>i&&p<=n&&(fr[p]-fr[i-1])==1)combine(i,p);
	}
	for(int i=1;i<=n;i++)
	{
		int fi=findf(i);
		ans=max(ans,abs(r[fi]-l[fi]));
	}
	cout<<ans;
	return 0;
}
posted @   KS_Fszha  阅读(11)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示