P9358题解
简化题意
给定一个 \(n \times (m+1)\) 的矩阵,开始时只有同一行有边,有 \(q\) 次操作,会在相邻的两行建一条边,或者是询问你从 \(x\) 行的第一列开始走,会走到哪里(有边必须走,不能走重复的边,保证路径唯一)。
做法
在模拟赛的时候想到了一个关于线段树的做法,就是对每一行开一棵线段树,然后对于一次建桥的操作,将 \(x\) 前面的全部修改为 \(y\) 的答案,对于 \(y\) 前面的全部修改为 \(x\) 的答案,但是复杂度有点爆炸,于是就去打暴力了。
但是赛后一想,发现我们其实只需要维护每一行开头的答案就可以了,即维护一个数组 \(to_i\) 表示从第 \(i\) 行开始可以到达的行。虽然查询是 \(O(1)\) 的,但是修改是 \(O(n)\) 的。我们需要想办法优化,想到了很久之前做过的一道分块题 P3203 [HNOI2010] 弹飞绵羊。所以我们可以对于 \(m\) 分块,则 \(to_{i,j}\) 表示从第 \(j\) 行的第 \(i\) 个块的左端点出发可以达到的下一个块的左端点的行,也就是从 \(j\) 行的 \(L_i\) 列出发,前往 \(R_i+1\) 列到达的行(\(L\),\(R\) 为块的左右端点)。
那么查询直接从 \(x,1\) 往前走就行了。至于修改,我们维护两个节点,\(s,t\) 分别从 \(x,x+1\) 行的第 \(y\) 列开始,往这个块的左端点走,如果有桥,就过桥,就可以找到从哪个节点出发可以到达 \(s,t\),将其交换即可。
查询经过了 \(\sqrt{m}\) 个块,修改经过了一个大小为 \(\sqrt{m}\) 的块,所以时间复杂度也就是 \(O(q\sqrt{m})\)。
代码
#include<bits/stdc++.h>
using namespace std;
const int N=100005,M=505;
int n,m,q;
int t;
int id[N],L[M],R[M];
int to[M][N];
map<int,int>g[N];
void update(int x,int y){
int yid=id[y];
g[x][y]=x+1;
g[x+1][y]=x;
int u=x,v=x+1;
for(int i=y;i>=L[yid];i--){
if(g[u].count(i))u=g[u][i];
if(g[v].count(i))v=g[v][i];
}
swap(to[yid][u],to[yid][v]);
return;
}
int query(int x){
for(int i=1;i<=t;i++)x=to[i][x];
return x;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n>>m>>q;
t=sqrt(m);t+=(t*t!=m);
for(int i=1;i<=t;i++){
L[i]=R[i-1]+1;
R[i]=min(m,i*t);
for(int j=L[i];j<=R[i];j++)id[j]=i;
for(int j=1;j<=n;j++)to[i][j]=j;
}
while(q--){
int op,x,y;cin>>op>>x;
if(op==1){
cin>>y;
update(x,y);
}else cout<<query(x)<<"\n";
}
return 0;
}

浙公网安备 33010602011771号