分析
依旧是一个连通块题。
观察题面不难发现两个重要性质:
- 一个跳棋只能以它旁边的两个跳棋为中点跳跃,且满足跳跃路线中 除中点以外没有其它跳棋阻挡。
- 只有我们的跳棋可以移动。
- 跳棋的操作具有可逆性/对称性。
第三条性质可以这么理解,就是一个跳棋跳过去之后,它还可以跳回来。
因此,无论我们从左右两边的哪一边开始起跳,都可以跳出最佳路径。
于是我们规定跳棋只能向右跳。(你规定向左跳也可以)
然后发现,跳棋跳的路线,是可以抽象成一棵树的。
每次从 \(u\) 点跳向 \(v\) 点,可以看作 \(v\) 为父亲节点,\(u\) 为儿子节点,两者连一条边。
这样树就建好了。
但这样仍然会超时,因为每次我们从树的叶子节点进入,进行查询操作,每个格子进行一遍,就相当于是 \(O(n^2)\) 的复杂度了。
并且还有一个性质,就是我们只注重跳到的最后的结果。
所以我们想到既可以实现 \(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;
}
标签:200005,int,题解,可以,查集,jp,HT,跳棋
From: https://www.cnblogs.com/zhr0102/p/18287458