【洛谷4739】[CERC2017] Donut Drone(线段树+倍增)
- 一个\(n\times m\)的循环矩阵,每个位置上有一个互不相同的权值。
- 每次会走到右、右上、右下中权值最大的格子。
- 初始在\((1,1)\),\(q\)次操作,分为两种:从当前位置出发走\(k\)步,并输出到达的位置;修改某个格子上的权值。
- \(n,m\le2000,q\le5000,k\le10^9\)
倍增
\(k\)这么大一看就是倍增。
但我们显然不可能对每个位置都倍增预处理一遍,因为一次修改影响范围可能非常广。
考虑到\(n,m,q\)都非常小,实际上我们可以先暴力走到第一列,记\(f_{i,j}\)表示从\((i,1)\)出发走\(2^j\)轮后会走到\((f_{i,j},1)\),倍增后再暴力走完余数部分即可。
这样一来要倍增的位置只有\(n\)个,即便一次修改使得它们全部发生变化都没关系。
线段树
对于线段树上一个区间\([l,r]\)开一个数组\(v[rt][i]\),表示从\((i,l)\)出发会走到\((v_i,r)\)。
合并的时候先求出\((v[lc][i],mid)\)下一步会走到的位置\((w,mid+1)\),那么\(v[rt][i]=v[rc][w]\)。
那么\((v[1][i],m)\)下一步会走到的位置就是\((f[i][0],1)\)了。
代码:\(O(nqlogn)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 2000
#define pre(x) (x^1?x-1:n)
#define nxt(x) (x^n?x+1:1)
using namespace std;
int n,m,a[N+5][N+5],f[N+5][31];
I int Q(CI x,CI y)//询问(x,y)下一个位置的行号
{
RI tx=x,ty=y^m?y+1:1;return a[pre(x)][ty]>a[tx][ty]&&(tx=pre(x)),a[nxt(x)][ty]>a[tx][ty]&&(tx=nxt(x)),tx;
}
class SegmentTree
{
private:
#define PT CI l=1,CI r=m,CI rt=1
#define LT l,mid,rt<<1
#define RT mid+1,r,rt<<1|1
int V[N<<2][N+5];I void PU(CI rt,CI mid) {for(RI i=1;i<=n;++i) V[rt][i]=V[rt<<1|1][Q(V[rt<<1][i],mid)];}//合并
public:
I void Bd(PT)//建树
{
if(l==r) {for(RI i=1;i<=n;++i) V[rt][i]=i;return;}RI mid=l+r>>1;Bd(LT),Bd(RT),PU(rt,mid);
}
I void U(CI y,PT)//更新
{
if(l==r) return;RI mid=l+r>>1;y<=mid?U(y,LT):U(y,RT),PU(rt,mid);
}
I void Get()//倍增预处理
{
RI i,j;for(i=1;i<=n;++i) f[i][0]=Q(V[1][i],m);for(j=1;j<=30;++j) for(i=1;i<=n;++i) f[i][j]=f[f[i][j-1]][j-1];
}
}S;
int main()
{
RI i,j;for(scanf("%d%d",&n,&m),i=1;i<=n;++i) for(j=1;j<=m;++j) scanf("%d",&a[i][j]);S.Bd(),S.Get();
RI Qt,px=1,py=1,x,y,k,t;char s[10];scanf("%d",&Qt);W(Qt--)
{
if(scanf("%s",s),s[0]=='c') {scanf("%d%d%d",&x,&y,&k),a[x][y]=k,S.U(y^1?y-1:m),S.Get();continue;}//直接修改,然后更新
scanf("%d",&k);W(k&&py^1) px=Q(px,py),++py>m&&(py=1),--k;t=k/m,k%=m;//暴力走到第一列
for(i=0;i<=30;++i) t>>i&1&&(px=f[px][i]);W(k) px=Q(px,py),++py>m&&(py=1),--k;printf("%d %d\n",px,py);//倍增后暴力走余数
}return 0;
}
待到再迷茫时回头望,所有脚印会发出光芒