6278. 2019.8.5【NOIP提高组A】跳房子

Description

Input

Output

Sample Input

4 4
1 2 9 3
3 5 4 8
4 3 2 7
5 8 1 6
4
move 1
move 1
change 1 4 100
move 1 

Sample Output

4 2
1 3
1 4 

Data Constraint

Solution

一道好题。

从看到题目到将这题AC,经过了36个小时,在这期间,我吃过饭,睡过觉,打过球,想过题,最终还是将这题做对了,但是那天的最后一题还是没有做对,尽管如此,这道题目还是值得好好收藏。

若是不打线段树版的像我一样就真的是百里挑一的题目了。。。

20%:直接模拟,O(QK)

40%:N*M之内必定有循环节,找出循环节再一步步走,O(NMQ)

100%:

解法一:

设jump[i]表示从第一列的第i行走m步所能到达的第一列的第几行。我们要不断维护jump数组

首先对于查询操作只要先用jump[]m步m步跳直到找到循环节,将Kmod(循环节长度*m),再用jump[]m步m步走,最后不足m步一步一步走,所以时间是O(N+M)的。

接下来我们再来考虑修改操作。

对于每个需要更新的(x,y),我们要找到所有经过(x,y)的点的路径,然后对于这些路径都修改他们的jump[]值,如何找呢?

想法一:

我们可以知道从第一列开始能走到(x,y)的行一定是一段连续的区间(超过n的从1开始往后接下去),所以我们可以首先找到(x,y)这个点往后(第m列)走走回第一列时是走到哪一列(设为num)。然后再从(x,y)往前(第一列)走,在每一列时不断维护一个区间[L,R],表示从这个区间的点都能走到(x,y),这样不断走,不断扩展L和R,然后走到第一列我们就将这段区间的jump[]都改成num就可以了。

想法二:

那么如何扩展L和R?

最初时,我是想先判断前一列的第L行的数(1)是否走到[L,R]这个区间范围内,(L>R特殊处理),若不能则直接L+1,若可以则继续看前一列的上一个位置(2),这时我只用判断这个位置2能否走到L就行了,如果能,就将L-1(边界特殊处理)。对于R端点也是同理。

对与R的1位置若步能跳到[L,R]则R-1,能则看2,2也能则R+1。

想法三:

上述过程中我们将L和R不断更新,即使小于1了也变成n,这样就会导致当L在R的下面时,我们不知道是L通过不断往上走走到R的下面的(或者R往下走走到了L的上面),还是[L,R]这个区间不存在了,即由L=R时,L-1或R-1得来。那么这个时候就有问题了,所以我们可以记录一个虚拟区间,即[l,r]区间记录它们的真实值而不参与小于1就走到N或大于N就走到1的操作(即r+-l+-),然后我们可以直接用这个值对应到1~N的区间中,也可以直接用[L,R]这个区间,这样我们就可以判断如果区间长度r-l+1大于0就为上述第一种情况,[L,R]这个区间还存在,反之,[L,R]这个区间已经不存在了,直接退出。

想法四:

还有注意到一个细节,我们如果直接更新L和R,不如在更新完L之后,这样会对判断R的位置(1)(2)是否能在[L,R]的范围内造成影响,因此我们需要先用两个变量将L,R存下来(设为L1,R1),当更新完L1,R1后,再赋给[L,R]。

想法五:

假设我们现在要修改的点为(x,y),假设这个点的值很大,原先前三个点都要走到(x,y)(如图中黑线),但是修改之后它的值就变小了,于是前一列的三个点就要跳到如图红线的位置,这样也会对第一列的jump[]值造成影响,所以,我们往前扩展的并不是从(x,y)这个点开始,而是从前一列的三个点开始,那么区间就变成了[x-1,x+1]?不,我们找到(x,y)前一列的每个点往后跳能跳到的第一列的行数,再分别从这三个点的行为L和R往前跳,找到第一列的区间再更新,所以要做三遍。

再想一想一个细节,当我们在找一个点往后跳回到第一列的第几行时我们要不断判断y!=1时才继续走的,但如果修改点在第二列,那么我们找出修改点的前三个点是在第一列(y==1),这时我们就不会进入循环更新,所以我们可以先让y走一步,然后再判断是否等于1。

如果修改点在第一列呢?那么同理我们找出前一列即为第列的三个数,再往前扩展[L,R]区间。

想法六:

但是这时你会发现你还是错的,为什么?

有一个细节,我们假设我们要从数值为3的这个点开始往前扩展(我们已经找出了3往后跳跳到第一列的第几行),这时L=R=4,N=4,按照之前的做法,我们找L的前一列的同一行与上一行的点能否跳到[L,R]区间,我们发现前一列的同行不能跳到[4,4],于是我们就将L+1,L就变成了1,再看R,R的前一列同行同样不能跳到[4,4],所以我们就直接将R-1了,R=3,这时l=5,r=3,r-l+1<0因此这个区间已经不存在,退出。然而我们发现前一列的第一行能跳到当前这一列的第四行,这样我们就完美的错过了答案,为什么会出现这种情况?是因为当L=R时,有可能它前一列的同行不能到达L但它上面可以到达,R同理,所以对于这种情况我们要特殊处理,即暴力修改L和R的值。只有L=R才有这种情况吗?

想法七:

事实上,只有L=R才有这种情况。

我们可以证明当区间[L,R]的长度大于1是上述情况不会出现,即更新L时前一列的上一行能走到[L,R]区间而同一行走不到,R同理。

证明大致如下:

假设区间[L,R]长度为2。

因为如果更新L是前一列的上一行的点能走到[L,R]区间(即2位置),那么它必定能走到这一列的L,且不走到这一列的L上面的两个点,那么对于上一列的同行的那个点(即1位置)可以走的点是L,R和L上面的一个点,因为2位置的点走到了L,说明L的值一定比L上面的位置的值大,因此1位置不会走到L上面的那个位置,所以1位置一定走到L或R,所以1位置走到[L,R]区间。对于R的扩展同理。

证毕。

我们可以发现如果L=R时上面的情况因为如果位置2走到了L,但是位置1可以走到L和L下面的点,因此就会有问题。R同理。

至此,我们只需加上L=R的特判即可。

修改的时间复杂度为O(N+M)。

因此,此解法的总时间复杂度为O(Q(N+M))。

解法二:

我们可以用线段树维护jump数组,对于线段树上的每一个区间[L,R],我们维护一个长度为n的jump[]数组,表示从第L列的每一个位置跳R-L次,跳到R+1列能跳到哪一行,对于更新只需将两个区间的jump[jump[]]直接赋值即可,因为从L1列 跳到 R2+1列就相当于从L1 列跳到第R1+1列(即L2列),再跳到R2+1列。

对于修改操作我们只用不断在线段树上二分递归下去,知道找到修改点的前一列的三个点,将它们修改再从线段树往上更新就可以了。

时间复杂度为O(QN log N)。

Code

#include<cstdio>
#include<algorithm>
#include<cstring>
#define I int
#define F(i,a,b) for(I i=a;i<=b;i++)
#define N 2002
#define mem(a,b) memset(a,b,sizeof(a))
I rd(){
	I x=0;char ch=getchar();
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x;
}
char c;
I n,m,a[N][N],Q,x1,x2,x3,y1,y2,y3,X1,Y1,X2,Y2,X3,Y3,Mx,My,k,x,y,z,p,q,t,yy,bz[N],go[N];
void work(I &p,I &q){
	Y1=q+1;if(Y1>m) Y1=1;
	Y2=Y3=Y1;
	X1=X2=X3=p;X1--;X2++;
	if(!X1) X1=n;if(X2>n) X2=1;
	Mx=X2,My=Y2;
	if(a[X1][Y1]>a[X2][Y2]) Mx=X1,My=Y1;
	if(a[X3][Y3]>a[Mx][My]) Mx=X3,My=Y3;
	p=Mx,q=My;
}
I inc2(I x){if(x==n) return 1;return x+1;}
I dec2(I x){if(x==1) return n;return x-1;}
void inc(I &x){x=inc2(x);}
void dec(I &x){x=dec2(x);}
I main(){
	freopen("jump.in","r",stdin);
	freopen("jump.out","w",stdout);
	scanf("%d%d",&n,&m);
	F(i,1,n)
		F(j,1,m) a[i][j]=rd();
	Q=rd();
	F(i,1,n){
		p=i,q=1;
		F(j,1,m) work(p,q);
		go[i]=p;
	}
	p=q=1;
	while(Q--){
		c=getchar();
		while(c!='m'&&c!='c') c=getchar();
		if(c=='m'){
			while(c!=' ') c=getchar();                                                                      
			k=rd();x=p,y=q;
			while(k--){
				work(x,y);
				if(!k||y==1) break;
			}//让(p,q) 走到第一列 
			if(!k) printf("%d %d\n",x,y);
			else{
				F(i,1,n) bz[i]=0;
				bz[x]=t=1;
				while(!bz[go[x]]&&k>=m){
					x=go[x];
					bz[x]=++t;
					k-=m;
				}//找n*m循环节
				if(bz[go[x]]&&k>=m){
					x=go[x];
					t=t+1-bz[x];
					k-=m;
					k-=(t*m)*(k/(t*m));
				}//循环节循环节步走 
				while(k>=m){
					x=go[x];
					k-=m;
				}//m步m步走 
				while(k--) work(x,y);//不足m步,一步一步走
				printf("%d %d\n",x,y);
			}
			p=x,q=y;
		}
		else{
			while(c!=' ') c=getchar();
			x=rd();y=rd();z=rd();
			a[x][y]=z;I X,Y,l,r,ll,rr,l1,r1;if(y==1) y=m;else y--;yy=y;
			F(j,1,3){
				Y=y=yy;
				if(j==1) X=dec2(x);
				if(j==2) X=x;
				if(j==3) X=inc2(x);
				l=r=ll=rr=X;
				while(1){
					work(X,Y);
					if(Y==1) break;
				}//找出(X,Y)能往后走完一轮回到第一列的第几个 
				I b=1;
				while(y!=1){
					l1=l,r1=r;
					if(l==r){
						y1=y2=y3=y-1;x2=l;
						x1=dec2(l);
						x3=inc2(l);
						l1=r1=0;
						work(x1,y1);
						work(x2,y2);
						work(x3,y3);
						if(x1==l){
							l1=l;r1=r;
							dec(l1);dec(r1);
							ll=rr=l-1;
						}
						if(x2==l){
							if(!l1) l1=l;
							r1=r;
						}
						if(x3==l){
							if(!l1){
								l1=l;inc(l1);
								ll=l+1;
							}
							r1=r;
							inc(r1);
							rr=r+1;
						}
						if(!l1&&!r1){b=0;break;}
						l=l1,r=r1;
						y--;
						continue;
					}
					y1=y2=y-1;
					x1=dec2(l);
					x2=l;
					work(x2,y2);
					if(l<=r){
						if(x2<l||x2>r){inc(l1),ll++;}
						else{
							work(x1,y1);
							if(x1==l){dec(l1),ll--;}
						}
					}
					else{
						if(x2<l&&x2>r){inc(l1),ll++;}
						else{
							work(x1,y1);
							if(x1==l){dec(l1),ll--;}
						}
					}
					y1=y2=y-1;
					x1=inc2(r);
					x2=r;
					work(x2,y2);
					if(l<=r){
						if(x2>r||x2<l){dec(r1),rr--;}
						else{
							if(rr-ll+1!=n){
								work(x1,y1);
								if(x1==r){inc(r1),rr++;}
							}
						}
					}
					else{
						if(x2>r&&x2<l){dec(r1),rr--;}
						else{
							if(rr-ll+1!=n){
								work(x1,y1);
								if(x1==r){inc(r1),rr++;}
							}
						}
					}
					l=l1,r=r1;
					if(ll>rr) b=0;
					if(!b) break;
					if(rr-ll+1>=n) break;
					y--;
				}
				//找出(x,y)能往前更新到第一列的连续的范围。 
				if(rr-ll+1>=n){
					F(i,1,n) go[i]=X;
					continue;
				}
				if(b){
					if(l<=r) F(i,l,r) go[i]=X;
					else{
						F(i,l,n) go[i]=X;
						F(i,1,r) go[i]=X;
					}
					//如果l和r能到达第一列则更新go[] 
				}
			}
		}
	}
	return 0;
}


作者:zsjzliziyang 
QQ:1634151125 
转载及修改请注明 
本文地址:https://blog.csdn.net/zsjzliziyang/article/details/98662822

发布了199 篇原创文章 · 获赞 201 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/zsjzliziyang/article/details/98662822