Loading

题解 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了

posted @ 2020-08-26 15:18  zzctommy  阅读(217)  评论(0编辑  收藏  举报