題解 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}\)
-
若 \(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了