1. 程式人生 > >[ NOI 2002 ] 銀河英雄傳說

[ NOI 2002 ] 銀河英雄傳說

\(\\\)

Description


\(n\) 列戰場,每一列一開始只有一個戰艦,編號就是對應的戰場編號。

\(m\) 次操作:

  • \(M_{i,j}\) :把 \(i\) 所在的一整列接在 \(j\) 所在的一列的最後面。
  • \(C_{i,j}​\) :查詢 \(i,j​\) 是否在同一列裡,若在的話輸出兩者之間隔了多少個戰艦。

注意每一列戰場的戰艦都是排成一列的。

\(\\\)

Solution


帶偏移量的並查集。

記錄 \(d[x]\) 表示 \(x\) 到所在列頭的一段上共有多少個戰艦。

記錄 \(sz[x]\) 表示 \(x\) 所在的一列有多少個戰艦。

為了保證複雜度,我們在做的時候還是要路徑壓縮。


但是 \(d[x]\) 怎麼保證正確?遞迴的時候這麼寫就好了。

int find(int x){
    if(x==f[x]) return x;
    int fa=find(f[x]);
    d[x]+=d[f[x]];
    return f[x]=fa;
  }

注意是 \(d[x]+=d[f[x]]\),因為之前可能路徑壓縮過。還有注意 $f[x] $ 的更新時間。


合併的時候,記 \(f_i\) 表示 \(i\) 所在一列的列頭,\(f_j\) 表示 \(j\) 所在的列頭,有
\[ d[f_i]=sz[f_j] \]


查詢的時候,\(find\) 的過程中已經保證了兩位置的 \(d\) 陣列數值正確,所以在一列的兩個戰艦答案就是
\[ |d[x]-d[y]|-1 \]

\(\\\)

Code


#include<cmath>
#include<cstdio>
#include<cctype>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 30010
#define R register
#define gc getchar
using namespace std;

inline int rd(){
  int x=0; bool f=0; char c=gc();
  while(!isdigit(c)){if(c=='-')f=1;c=gc();}
  while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
  return f?-x:x;
}

struct UFS{
  int f[N],d[N],sz[N];
  UFS(){for(R int i=1;i<N;++i){f[i]=i;sz[i]=1;}}
  int find(int x){
    if(x==f[x]) return x;
    int fa=find(f[x]);
    d[x]+=d[f[x]];
    return f[x]=fa;
  }
  inline void merge(int x,int y){
      int fx=find(x),fy=find(y);
      d[fx]=sz[fy]; f[fx]=fy; sz[fy]+=sz[fx];
  }
}ufs;

int main(){
  char op;
  int t=rd(),x,y;
  while(t--){
    op=gc(); while(!isalpha(op)) op=gc();
    if(op=='M'){x=rd();y=rd();ufs.merge(x,y);}
    else{
      x=rd(); y=rd();
      if(ufs.find(x)!=ufs.find(y)) puts("-1");
      else printf("%d\n",abs(ufs.d[x]-ufs.d[y])-1);
    }
  }
  return 0;
}