题解 P5397 【[Ynoi2018]天降之物】
lxl的博客讲的挺清楚了。
如果不管时间复杂度,能想到的最暴力的做法是什么?
用vector对于每个值维护出现的位置(从小到大),查询的时候暴力归并,修改也是暴力归并。
于是就可以获得60分的成绩(随机数据下跑的挺快的qwq),然后就可以看别人的AC代码了 。
再看看这题。暴力时间复杂度错误的原因是那个位置的集合太大了(按lxl的叫法,叫做附属集合)。那就把那个集合弄小一点嘛,那多大可以接受呢?这是Ynoi啊,当然是根号 。
于是就想到了 根号分治 。
设 \(siz[x]\) 表示 \(x\) 在集合中出现的次数,\(siz[x]>lim\) 的称作大点(\(lim\) 是阈值), 否则称为小点。
但是怎么维持附属集合的大小在 \(lim\) 以内呢?考虑在附属集合大小超过 \(lim\) 的时候直接 \(O(n)\) 暴力处理这个大点(显然是个大点,小点的附属集合不可能超过 \(lim\))到所有值的答案,设为 \(ans[x][y]\) , \(x\) 是大点, \(y\) 是每个值,然后清空 \(y\) 附属集合,于是就保证了所有附属集合大小不超过 \(lim\) 。后文把这个过程称作暴力重构。
显然 ,\(ans\) 的答案并不包括附属集合内的答案。
接下去的讨论默认 \(siz[x]<siz[y]\) ,因为有种技巧可以使得保证 \(siz[x]<siz[y]\) ,讨论结束了会讲。
对于修改,把 \(x\) 变成 \(y\) :
-
如果 \(x,y\) 均为小点,由于出现次数小于 \(lim\) ,直接暴力把 \(x\) 归并进 \(y\) 的附属集合,如果大于 \(lim\) 就把 \(y\) 设成大点,暴力重构 \(y\) ,由于大点显然最多 \(\dfrac{n}{lim}\) 个,这个过程不会超过 \(\dfrac{n}{lim}\) 次。
-
若 \(x\) 为小点, \(y\) 为大点
直接把 \(x\) 并入 \(y\) 的附属集合即可,\(y\) 的附属集合大小如果超过 \(lim\) 则暴力重构\(y\) 。
显然大点的个数不会超过 \(\dfrac{n}{lim}\) ,又发现附属集合元素数量总和不超过 \(n\) ,所以暴力重构次数不会超过 \(\dfrac{n}{lim}\) 。
-
若 \(x,y\) 均为大点,显然可以暴力重构 \(y\) ,因为这个过程使得 \(siz[y]\) 至少增大 \(lim\) ,不会超过 \(\dfrac{n}{lim}\) 次。
综上,修改的复杂度被控制在了 \(O(\dfrac{nm}{lim})\) 。
对于查询 \(x,y\) 的最近距离:
- 如果 \(x,y\) 均为小点,暴力归并 \(x,y\) 的附属集合。时间复杂度 \(O(lim)\) 。
- 如果 \(x\) 为小点, \(y\) 为大点,暴力归并 \(x,y\) 的附属集合,再与 \(ans[y][x]\) 取 \(\min\) 。时间复杂度 \(O(lim)\) 。
- 如果 \(x,y\) 均为大点,暴力归并 \(x,y\) 的附属集合,再与 \(ans[x][y],ans[y][x]\) 取 \(\min\) ,因为我们并不知道 \(x,y\) 哪个靠后。被重构,所以 \(ans[x][y],ans[y][x]\) 必须都要取 \(\min\) 。 时间复杂度 \(O(lim)\) 。
综上,查询的复杂度是 \(O(m\cdot lim)\) 。
如何保证 \(siz[x]<siz[y]\) 呢?我们加个数组 \(F[i]\) 表示 \(i\) 这个值实际是多少,初始化 \(F[i]=i\) 。
如果要交换 \(x,y\) 那么 \(F[x]=y,F[y]=x\) 即可 。
\(lim\) 取 \(\sqrt n\) 即可,实测 \(lim=500\) 更快,不过这题并不卡常。
注意事项
- 时刻记着 \(ans\) 是不包括附属集合的答案,不然代码会出一堆bug。
- 暴力重构的时候要扫 \(2\) 遍,从前到后再从后到前。
- 暴力重构完记得清空附属集合。
- 出题人好像没卡,强制在线解密以后以及原来的数组都没有 \(0\) 出现,但是建议不要漏掉 \(0\) 的情况。
- 最坑的一点了:vector 的 clear 并不释放空间。
所以清空vector应该这么写:
void fyyAKCTS() {
/*your code*/
vector<int>fyyAKIOI;
v[x].swap(fyyAKIOI);
}
因为 \(fyyAKIOI\) 是个局部变量,到了 \(fyyAKCTS\) 函数的右大括号会被释放空间,而此时由于 \(fyyAKIOI\) 原本空的,\(\operatorname{swap}\) 以后 \(v[x]\) 就是空的了,于是成功清空了vector并释放了空间 还因为尛了fyy加了rp 。
code:
const int N=100009;
const int M=320;
const int inf=0x3f3f3f3f;
int n,m,a[N],lastans;
int lim,siz[N],ans[M][N],id[N],F[N],tot;
vector<int>v[N];
void build(int x,int Id=0) {
id[x]=Id?Id:++tot;
int t=id[x];
memset(ans[t],0x3f,sizeof(ans[t]));
for(int i=1,j=inf;i<=n;++i)
if(a[i]==x)j=0;
else ans[t][a[i]]=min(ans[t][a[i]],++j);
for(int i=n,j=inf;i;--i)
if(a[i]==x)j=0;
else ans[t][a[i]]=min(ans[t][a[i]],++j);
vector<int>fyyAKIOI;
ans[t][x]=0,v[x].swap(fyyAKIOI);
}
void merge(int x,int y) {
int i=0,j=0,sx=v[x].size(),sy=v[y].size();
vector<int>res;
while(i<sx&&j<sy)res.push_back(v[x][i]<v[y][j]?v[x][i++]:v[y][j++]);
while(i<sx)res.push_back(v[x][i++]);
while(j<sy)res.push_back(v[y][j++]);
v[y]=res;
}
int merge_(int x,int y) {
int i=0,j=0,sx=v[x].size(),sy=v[y].size(),res=inf;
if(!sx||!sy)return inf;
while(i<sx&&j<sy)res=min(v[x][i]<v[y][j]?v[y][j]-v[x][i++]:v[x][i]-v[y][j++],res);
if(i<sx)res=min(res,abs(v[x][i]-v[y][sy-1]));
if(j<sy)res=min(res,abs(v[x][sx-1]-v[y][j]));
return res;
}
int query(int x,int y) {
x=F[x],y=F[y];
if(!siz[x]||!siz[y])return -1;
if(x==y)return 0;
if(siz[x]>siz[y])x^=y^=x^=y;
if(siz[y]<=lim)return merge_(x,y);
else if(siz[x]<=lim)return min(ans[id[y]][x],merge_(x,y));
else return min(merge_(x,y),min(ans[id[x]][y],ans[id[y]][x]));
}
void change(int x,int y) {
int x_=F[x],y_=F[y];
if(!siz[x_]||x_==y_)return;
if(siz[x_]>siz[y_])F[y]=x_,F[x]=n+1,x_^=y_^=x_^=y_;
else F[x]=n+1;
if(x_>n||y_>n)return;
x=x_,y=y_;
if(siz[y]<=lim) {
if(siz[x]+siz[y]<=lim) {
for(int i:v[x])a[i]=y;
for(int i=1;i<=tot;++i)ans[i][y]=min(ans[i][y],ans[i][x]);
merge(x,y);
} else {
for(int i=1;i<=n;++i)if(a[i]==x)a[i]=y;
build(y);
}
} else if(siz[x]<=lim) {
if(siz[x]+v[y].size()<=lim) {
for(int i:v[x])a[i]=y;
for(int i=1;i<=tot;++i)ans[i][y]=min(ans[i][y],ans[i][x]);
merge(x,y);
} else {
for(int i=1;i<=n;++i)if(a[i]==x)a[i]=y;
build(y,id[y]);
}
} else {
for(int i=1;i<=n;++i)if(a[i]==x)a[i]=y;
build(y,id[y]);
}
vector<int>fyyAKIOI;
siz[y]+=siz[x],siz[x]=0,v[x].swap(fyyAKIOI);
}
signed main() {
n=rd(),m=rd(),lim=sqrt(n);
for(int i=1;i<=n;++i)++siz[a[i]=rd()],v[a[i]].push_back(i),F[i]=i;
for(int i=0;i<=n;++i)if(siz[i]>lim)build(i);
while(m--) {
int opt=rd(),x=rd()^lastans,y=rd()^lastans;
if(opt==1)change(x,y);
else {
lastans=query(x,y);
if(~lastans)printf("%d\n",lastans);
else lastans=0,puts("Ikaros");
}
}
return 0;
}
由于用了fyyAKIOI还AC了,说明fyy真的AKIOI了