ある特性を知っているか否かで極端に難易度が変わる問題。
http://codeforces.com/contest/379/problem/F
問題
1番の頂点を根とする木があり、初期状態ではそこに2・3・4番の点が接続されている。
この後Q個のクエリが与えられる。
各クエリでは既存の頂点番号vが与えられ、そこに新たな頂点N+1・N+2を接続する(Nはそれまでの頂点数)。
各クエリ処理後の木の直径(最長の頂点間距離)を示せ。
解法
どうもこれは最近別のコンテストで類似した問題が出たようだ。
以下の特性を利用すると簡単に解ける。
「現在の直径が頂点A,B間の距離だった場合、そこに頂点Cを付け加えて直径が伸びる場合、その距離はA,C間またはB,C間のいずれかと等しい」
よって距離が最長の2点を覚えておき、クエリを処理するたびにそれら2点との距離を比較して、距離が長くなりそうなら「最長の2点」を入れ替えていけばよい。
2点間の距離は親子関係をdoublingし、Least Common Ancestorを処理することで頂点数がQに比例するので全体でO(QlogQ)で処理できる。
このテクニックは蟻本でも出てくる。
ついでにdoublingとLCA処理ををライブラリ化しておいた。
ともあれ最初の特性を知らないと簡単に解けないね。
int Q,N; int depth[1000010]; int pa[21][1<<20]; int dist(int x,int y) { int res=0,i; if(depth[x]<depth[y]) swap(x,y); res = depth[x]-depth[y]; // go same depth for(i=19;i>=0;i--) if(res&(1<<i)) x=pa[i][x]; for(i=19;i>=0;i--) if(pa[i][x]!=pa[i][y]) res += (1<<i)*2,x=pa[i][x],y=pa[i][y]; return res + 2*(x!=y); } void solve() { int f,i,j,k,l,x,y; int mia,mib,d; N=4; pa[0][1]=0; depth[1]=0; pa[0][2]=pa[0][3]=pa[0][4]=1; depth[2]=depth[3]=depth[4]=1; FOR(i,20) for(j=1;j<=N;j++) pa[i+1][j]=pa[i][pa[i][j]]; mia=2,mib=4,d=2; cin>>Q; FOR(j,Q) { cin>>x; pa[0][N+1] = pa[0][N+2] = x; depth[N+1] = depth[N+2] = depth[x]+1; FOR(i,20) pa[i+1][N+1]=pa[i][pa[i][N+1]]; FOR(i,20) pa[i+1][N+2]=pa[i][pa[i][N+2]]; if(dist(mia,N+1)>d) d=dist(mia,N+1),mib=N+1; else if(dist(mib,N+1)>d) d=dist(mib,N+1),mia=N+1; _P("%d\n",d); N+=2; } }
まとめ
それにしても、最初の4点とか各クエリで2点追加するあたりはこの問題の解法と全然関係ないな。