1. 程式人生 > 實用技巧 >題解 P5397 【[Ynoi2018]天降之物】

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