一応本番中に思いついた方針で後日最終的に自力で解いたけど、バグらせまくりで到底時間内には間に合わなかった。
http://codeforces.com/contest/613/problem/D
問題
N頂点からなる木を成すグラフが与えられる。
このグラフに対しQ個のクエリに解答せよ。
各クエリでは、N頂点中いくつか重要な頂点が指定される。
いくつかの(重要でない)頂点を塞ぐことで、重要な頂点同士が辺を辿って到達できないようにしたい。
各クエリについて、塞ぐべき最少頂点数を求めよ。
解法
まず木を根付き木とし、EulerTourで順序付けした上で、LCAを求められるよう前処理しておく。
各クエリについて、まず重要な頂点をEulerTour順にソートしよう。
これらの頂点列について、以下の処理を行う。
- (頂点列中で)隣接する頂点同士のLCAを求め、一番深いものを求める。
- その隣接点同士が重要な点に接続されているなら、このLCAは塞ぐべきである。そうでないなら塞がなくてよい。
- 塞ぐにせよ塞がないにせよ、そのLCAのSubTree中にLCAに到達可能な頂点が0個か1個かを順次更新する。そして頂点列中の処理した2つの隣接点をLCAで置き換える。
上記処理を頂点が1個になるまで行う。
LCAの深さ順ソートはsetで行えば間に合う。
数列中で隣接する2点をLCAに置き換える、という処理は、双方向リンクリストを用いれば高速に数列の要素削減ができる。
注意点として、LCAがすでに重要な点の場合、そこを塞ぐことができない。
例えば(頂点列中で)隣接する2頂点をx,yとしたとき、xがyの祖先でかつxが重要な場合、xとyの距離が2以上あれば間の頂点を塞げばよい。
yがxの個であると塞げないので、条件を満たす塞ぎ方はできない。
const int ME=300005; vector<int> LL[ME]; int L[ME],R[ME],eid; int ST[ME]; int N,Q,K; vector<int> E[200005]; int P[21][200005],D[200005]; void dfs(int cur) { ITR(it,E[cur]) if(*it!=P[0][cur]) D[*it]=D[cur]+1, P[0][*it]=cur, dfs(*it); } int getpar(int cur,int up) { int i; FOR(i,20) if(up&(1<<i)) cur=P[i][cur]; return cur; } int lca(int a,int b) { int ret=0,i,aa=a,bb=b; if(D[aa]>D[bb]) swap(aa,bb); for(i=19;i>=0;i--) if(D[bb]-D[aa]>=1<<i) bb=P[i][bb]; for(i=19;i>=0;i--) if(P[i][aa]!=P[i][bb]) aa=P[i][aa], bb=P[i][bb]; return (aa==bb)?aa:P[0][aa]; // vertex } void EulerTour(int cur,int pre=-1) { int i; FOR(i,E[cur].size()) if(E[cur][i]==pre) { E[cur].erase(E[cur].begin()+i); break; } L[cur]=eid++; ITR(it,E[cur]) { EulerTour(*it,cur); LL[cur].push_back(L[*it]); } R[cur]=eid; } int hoge(vector<int>& V,int st) { int i,N; N=V.size(); vector<pair<int,int>> VV; FORR(r,V) VV.push_back({L[r],r}); sort(VV.begin(),VV.end()); FOR(i,N) V[i]=VV[i].second; set<pair<int,int>> Q; FOR(i,N-1) Q.insert({-D[lca(V[i],V[i+1])],i}); vector<pair<int,int>> nex; FOR(i,N) nex.push_back({i-1,i+1}); nex[0].first=0; nex[N-1].second=N-1; int cost=0; while(Q.size()) { auto r=*Q.begin(); Q.erase(Q.begin()); int x=r.second; int y=nex[x].second; if(x!=nex[x].first) Q.erase({-D[lca(V[x],V[nex[x].first])],nex[x].first}); if(y!=nex[y].second) Q.erase({-D[lca(V[y],V[nex[y].second])],y}); int lc=lca(V[x],V[y]); if(abs(ST[lc])<st) ST[lc]=0; if(ST[V[x]]==st && ST[V[y]]==st && P[0][V[y]]==V[x]) return -1; if(ST[V[x]]>=st && ST[V[y]]>=st) cost++; if(ST[lc]==0 || ST[lc]%2) { if(ST[V[x]]>0 && ST[V[y]]>0) ST[lc]=-st; else if(ST[V[x]]<0 && ST[V[y]]<0) ST[lc]=-st-1; else ST[lc]=st+1; } V[x]=lc; if(x!=nex[x].first) Q.insert({-D[lca(V[x],V[nex[x].first])],nex[x].first}); if(y!=nex[y].second) { nex[nex[y].second].first=x; nex[x].second=nex[y].second; Q.insert({-D[lca(V[x],V[nex[y].second])],x}); } else nex[x].second=x; } return cost; } void solve() { int i,j,k,l,r,x,y; string s; int go=0; cin>>N; FOR(i,N-1) { cin>>x>>y; if(i==0 && x==2) go=1; E[x-1].push_back(y-1); E[y-1].push_back(x-1); } dfs(0); FOR(i,19) FOR(x,N) P[i+1][x]=P[i][P[i][x]]; EulerTour(0); cin>>Q; int st; FOR(st,Q) { cin>>K; vector<int> V; FOR(i,K) cin>>x, x--, V.push_back(x), ST[x]=(st+1)*2; cout<<hoge(V,(st+1)*2)<<endl; } }
まとめ
双方向リンクリストと、LCAの深さ順に処理する方針はすぐ立ったけど、後の頂点の状態遷移に大混乱。