Gym101620D Donut Drone
Link
我们知道每走一步一定会往前走一列。
考虑求出\(next_i\)表示从\((i,1)\)开始走,下一次走到第一列的时候在第几行。
可以先预处理\(a_{i,j}=[l,r]\)表示\(\forall k\in[l,r]\),从\((k,1)\)开始走,会走到\((i,j)\),不难发现这一定是个区间。
\(O(rc)\)地预处理\(a\)后,就可以\(O(r)\)地计算\(next\)了。
对于修改\((x,y,v)\),第\(y\)列中\(a\)发生变动的点的一定在\([x-2,x+2]\)行。
那么我们可以对于这\(5\)个点,暴力往后修改\(a\),单次复杂度为\(O(c)\)。
对于询问,考虑\(x\rightarrow next_x\)会构成一棵内向基环树。
我们先计算完整地走完了多少次所有列,再计算这样会在基环树上到达哪个点,然后再暴力往后走即可,单次复杂度为\(O(r+c)\)。
总的时间复杂度为\(O(rc+q(r+c))\)。
#include<cstdio>
#include<cctype>
#include<cstring>
#include<utility>
#define fi first
#define se second
using pi=std::pair<int,int>;
const int N=2007;
int r,c,next[N],val[N][N],num[N];pi a[N][N];
char get(){char c=getchar();while(!islower(c))c=getchar();return c;}
int read(){int x=0,c=getchar();while(!isdigit(c))c=getchar();while(isdigit(c))(x*=10)+=c&15,c=getchar();return x;}
int pre(int x){return x==1? r:x-1;}
pi merge(pi p,pi q){return ~p.fi? (~q.fi? (p.se%r+1==q.fi? pi{p.fi,q.se}:pi{q.fi,p.se}):p):q;}
int go(int x,int y)
{
y=y%c+1;int mx=0,id=0;
for(int i=0,p=pre(x);i<3;++i,p=p%r+1) if(mx<val[p][y]) mx=val[id=p][y];
return id;
}
void cal(int x,int y)
{
if(y==1) return a[x][y]={x,x},void();
a[x][y]={-1,-1};
for(int i=0,p=pre(x);i<3;++i,p=p%r+1) if(go(p,y-1)==x) a[x][y]=merge(a[x][y],a[p][y-1]);
}
void build()
{
for(int i=1,t;i<=r;++i)
{
if(!~a[i][c].fi) continue;
next[a[i][c].fi]=t=go(i,c);
for(int j=a[i][c].fi%r+1;j!=a[i][c].se%r+1;j=j%r+1) next[j]=t;
}
}
void rebuild(int x,int y){for(;y^1;x=go(x,y),y=y%c+1)cal(x,y);}
int main()
{
r=read(),c=read();
for(int i=1;i<=r;++i) for(int j=1;j<=c;++j) val[i][j]=read();
for(int j=1;j<=c;++j) for(int i=1;i<=r;++i) cal(i,j);
build();
for(int Q=read(),x=1,y=1;Q;--Q)
if(get()=='m')
{
int k=read(),now=1;memset(num,0,sizeof num);
while(k&&y^1) x=go(x,y),y=y%c+1,--k;
while(k>=c&&!num[x]) num[x]=now++,x=next[x],k-=c;
for(k%=(now-num[x])*c;k>=c;) k-=c,x=next[x];
while(k--) x=go(x,y++);
printf("%d %d\n",x,y);
}
else
{
int x=read(),y=read(),v=read();val[x][y]=v;
for(int i=0,p=pre(pre(x));i<5;++i,p=p%r+1) rebuild(p,y);
build();
}
}