ECRの後半は、方針が立っても実装が重いものが多い気がするなぁ。
https://codeforces.com/contest/1140/problem/F
問題
2次元座標上で、格子点の位置に点を追加・削除するクエリが与えられる。
今ある点の集合をSとする。
3点が、軸に平行な長方形の3頂点に位置するとき、残る1点をSに追加する、という処理をそれ以上点が増えなくなるまで続けた場合を仮定し、その状態の点の集合をE(S)とする。
Sの変化に対し、E(S)を随時答えよ。
解法
まず、点の追加だけの場合を考える。
X座標とY座標に対応する点群からなる二部グラフを考える。
Sへの点の追加とは、X座標とY座標に対応する辺が追加されるものだとみなそう。
このグラフにおいて、長さ3のパスがあった場合、それは問題に示す3頂点の組み合わせがあることに相当する。
よってE(S)に対応するグラフを考えると、そのパスの両端を結ぶ辺を追加することに相当する。
また、E(S)が変化なくなるまで辺を増やすと、結局Sに対応するグラフにおける連結成分では、要素間にフルメッシュで辺が張られることになる。
よって、各連結成分に対し、(X座標に対応する点の数)と(Y座標に対応する点の数)の積の総和が解となる。
なので、点の追加クエリだけなら、UnionFindを使い、そこに(X座標に対応する点の数)と(Y座標に対応する点の数)を持つようにしてその積和を更新していけば、解ける。
この上で削除クエリを考えよう。
削除の順番は追加順とは異なるので、良くある巻き戻し可能なUnion-Findは使えない。
そこで、その点が追加されている区間を時系列で考える。
そこでSegTreeを考える。このSegTreeはクエリ実行順の時間をKeyとし、そのNodeにその時間の間有効である辺を載せることにする。
つまり、1個の辺が有効な区間を、SegTreeを用いて複数の時間区間に分割することにする。
あとはこのSegTree上をDFS順で辿るようにすれば、巻き戻し可能なUnion-Findで処理できる。
なお、このテクはyukicoderのwikiに記載済みとcamypaperさんから教えていただきました。知らんかった…。
Decomposable searching problem - オフラインの場合 Wiki - yukicoder
int Q; int X[303030],Y[303030]; ll cur; map<pair<int,int>,int> m; ll ret[(1<<19)+5]; template<int um> class UF_pop { public: vector<int> par,rank,Xs,Ys; vector<vector<int>> hist; UF_pop() {par=rank=Xs=Ys=vector<int>(um,0); for(int i=0;i<um;i++) par[i]=i; for(int i=0;i<300000;i++) Xs[i]=1,Ys[i+300000]=1; } void reinit() {int i; FOR(i,um) rank[i]=0,par[i]=i;} int operator[](int x) {return (par[x]==x)?(x):operator[](par[x]);} void pop() { if(hist.size()) { auto a=hist.back(); hist.pop_back(); par[a[0]]=a[1]; rank[a[0]]=a[2]; Xs[a[0]]=a[3]; Ys[a[0]]=a[4]; par[a[5]]=a[6]; rank[a[5]]=a[7]; Xs[a[5]]=a[8]; Ys[a[5]]=a[9]; cur-=1LL*(Xs[a[0]]+Xs[a[5]])*(Ys[a[0]]+Ys[a[5]]); cur+=1LL*Xs[a[0]]*Ys[a[0]]+1LL*Xs[a[5]]*Ys[a[5]]; } } void operator()(int x,int y) { x=operator[](x); y=operator[](y); hist.push_back({x,par[x],rank[x],Xs[x],Ys[x],y,par[y],rank[y],Xs[y],Ys[y]}); if(x==y) return; if(rank[x]<rank[y]) par[x]=y; else rank[x]+=(rank[x]==rank[y]), par[y]=x; cur-=1LL*Xs[x]*Ys[x]+1LL*Xs[y]*Ys[y]; cur+=1LL*(Xs[x]+Xs[y])*(Ys[x]+Ys[y]); Xs[x]=Xs[y]=Xs[x]+Xs[y]; Ys[x]=Ys[y]=Ys[x]+Ys[y]; } }; UF_pop<606060> uf; template<int NV> class SegTree_2 { public: vector<vector<pair<int,int>>> ev; SegTree_2(){ev.resize(NV*2);}; void update(int x,int y, pair<int,int> e,int l=0,int r=NV,int k=1) { if(l>=r) return; if(x<=l && r<=y) { ev[k].push_back(e); } else if(l < y && x < r) { update(x,y,e,l,(l+r)/2,k*2); update(x,y,e,(l+r)/2,r,k*2+1); } } void dfs(int k=1,int L=0,int R=NV) { int num=0; FORR(e,ev[k]) { if(uf[e.first]!=uf[e.second]) { uf(e.first,e.second); num++; } } if(L+1==R) { ret[L+1]=cur; } else { dfs(k*2,L,(L+R)/2); dfs(k*2+1,(L+R)/2,R); } while(num--) uf.pop(); } }; SegTree_2<1<<19> st; void solve() { int i,j,k,l,r,x,y; string s; cin>>Q; FOR(i,Q) { cin>>X[i]>>Y[i]; X[i]--; Y[i]--; Y[i]+=300000; if(m.count({X[i],Y[i]})) { st.update(m[{X[i],Y[i]}],i,{X[i],Y[i]}); m.erase({X[i],Y[i]}); } else { m[{X[i],Y[i]}]=i; } } FORR(e,m) { st.update(e.second,Q+1,e.first); } st.dfs(); FOR(i,Q) cout<<ret[i+1]<<" "; cout<<endl; }
まとめ
本番も削除ができない…とずっと唸ってました。