4.19 省选模拟赛 跳跃 倍增 二分 线段树 建图

时间:2020-04-20
本文章向大家介绍4.19 省选模拟赛 跳跃 倍增 二分 线段树 建图,主要包括4.19 省选模拟赛 跳跃 倍增 二分 线段树 建图使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

LINK:跳跃


不算难想的题目 考试的时候没想出来 还是想的太少 思路被束缚住了。

第一个想法 二分 发现check的时候还是需要枚举点对来算距离什么的 然后弃掉。

计算过样例后发现一个点到达右边可能先去左边再一下子跳到右边。

直接建图bfs。

发现这样做是n^3的 直接线段树优化建图了。 复杂度n^2log.

const int MAXN=3010;
int n,maxx,len,cnt,root,id;
int a[MAXN];
struct wy{int l,r;}t[MAXN<<2];
int b[MAXN][MAXN],vis[MAXN*MAXN],dis[MAXN*MAXN];
int lin[MAXN*MAXN],ver[MAXN*MAXN],nex[MAXN*MAXN],e[MAXN*MAXN];
inline void add(int x,int y,int z)
{
	ver[++len]=y;
	nex[len]=lin[x];
	lin[x]=len;
	e[len]=z;
}
inline void build(int &p,int l,int r)
{
	if(!p)p=++cnt;
	if(l==r){add(p,l,1);return;}
	int mid=(l+r)>>1;
	build(l(p),l,mid);
	build(r(p),mid+1,r);
	add(p,l(p),0);add(p,r(p),0);
}
inline void change(int p,int l,int r,int L,int R,int x)
{
	if(L<=l&&R>=r)
	{
		add(x,p,0);
		return;
	}
	int mid=(l+r)>>1;
	if(L<=mid)change(l(p),l,mid,L,R,x);
	if(R>mid)change(r(p),mid+1,r,L,R,x);
	return;
}
deque<int> q;
inline void bfs(int w)
{
	++id;q.pf(w);dis[w]=0;vis[w]=id;
	while(q.size())
	{
		int x=q.front();
		q.popf();
		go(x)
		{
			if(vis[tn]!=id)
			{
				vis[tn]=id;
				dis[tn]=dis[x]+e[i];
				if(!e[i])q.pf(tn);
				else q.pb(tn);
			}
		}
	}
}
int main()
{
	//freopen("jumping.in","r",stdin);
	//freopen("jumping.out","w",stdout);
	get(n);
	if(n<=3000)
	{
		cnt=n;build(root,1,n);
		for(int i=1;i<=n;++i)
		{
			int L,R;get(a[i]);
			L=max(i-a[i],1);
			R=min(i+a[i],n);
			change(root,1,n,L,R,i);
		}
		rep(1,n,i)
		{
			bfs(i);
			rep(1,n,j)b[i][j]=dis[j];
		}
		int ans=0;
		rep(1,n,i)rep(1,i,j)ans=max(ans,min(b[i][j],b[j][i]));
		put(ans);return 0;
	}
	return 0;
}

我发现除了枚举点对之外没有什么好方法 剩下的时间就在研究无向图的直径/有相图的直径 下界复杂度都是nm的 放弃治疗。

还是考虑二分 二分出答案之后 我们需要得到两个点对之间跳mid步都达不到对方。

\(L_{i,j}\)表示i这个点跳j步所能到达最左端的地方。

容易发现这个东西可能可以反复横跳 所以还需要一个数组 \(R_{i,j}\)表示i这个点跳j步所能达到最右端的地方。

转移显然。

现在有两个问题需要解决一个是L R数组如何快速求出 一个是如何判定答案。

考虑前者 容易想到倍增。正确性显然 这样就可以在nlog^2的时间内求了 注意利用线段树维护 好写一点。

考虑后者 如果答案还可以更大的话 那么必然两个点x,y(x<y) 满足x跳mid步到最右端<y y跳mid步到最左端>x.

这样check即可。

不过可以发现对于二分一个mid,log.我们需要先用倍增数组拼成mid log.拼一次nlog.

复杂度nlog^3. 一个小trick 直接倍增求答案 然后这样就省掉了二分。复杂度nlog^2.

注意 判定答案的时候 一定要严格>和< 如果不严格 可能对于边界 多跳若干部也还是边界等等。

const int MAXN=200010;
int n,ans;
struct wy{int l,r,sum;}t[MAXN<<2];
int a[MAXN],Log[MAXN],w[MAXN],bl[MAXN],br[MAXN],c[MAXN];
int L[MAXN][20],R[MAXN][20],ansl[MAXN],ansr[MAXN],wl[MAXN],wr[MAXN];
inline void build(int p,int l,int r)
{
	l(p)=l;r(p)=r;
	if(l==r){sum(p)=w[l];return;}
	int mid=(l+r)>>1;
	build(zz,l,mid);
	build(yy,mid+1,r);
	sum(p)=min(sum(zz),sum(yy));
}
inline int ask(int p,int l,int r)
{
	if(l<=l(p)&&r>=r(p))return sum(p);
	int mid=(l(p)+r(p))>>1;
	if(r<=mid)return ask(zz,l,r);
	if(l>mid)return ask(yy,l,r);
	return min(ask(zz,l,r),ask(yy,l,r));
}
inline int check()
{
	fep(n,1,i)c[i]=max(c[i+1],wl[i]);
	rep(1,n,i)if(wr[i]!=n&&c[wr[i]+1]>i)return 1;
	return 0;
}
int main()
{
	freopen("1.in","r",stdin);
	get(n);Log[0]=-1;
	rep(1,n,i)get(a[i]),ansl[i]=ansr[i]=i,L[i][0]=max(1,i-a[i]),R[i][0]=min(n,i+a[i]),Log[i]=Log[i>>1]+1;
	rep(1,Log[n],j)
	{
		rep(1,n,i)w[i]=L[i][j-1];
		build(1,1,n);
		rep(1,n,i)L[i][j]=ask(1,L[i][j-1],R[i][j-1]);
		rep(1,n,i)w[i]=-R[i][j-1];
		build(1,1,n);
		rep(1,n,i)R[i][j]=-ask(1,L[i][j-1],R[i][j-1]);
	}
	fep(Log[n],0,j)//形成答案集合.
	{
		rep(1,n,i)bl[i]=ansl[i],br[i]=ansr[i];
		rep(1,n,i)w[i]=L[i][j];
		build(1,1,n);
		rep(1,n,i)wl[i]=ask(1,bl[i],br[i]);
		rep(1,n,i)w[i]=-R[i][j];
		build(1,1,n);
		rep(1,n,i)wr[i]=-ask(1,bl[i],br[i]);
		if(check())
		{
			rep(1,n,i)ansl[i]=wl[i],ansr[i]=wr[i];
			ans=ans|(1<<j);
		}
	}
	put(ans+1);
	return 0;
}

原文地址:https://www.cnblogs.com/chdy/p/12738391.html