kmjp's blog

競技プログラミング参加記です

Codeforces ECR #112 : F. Good Graph

Eまでは比較的易しめだったんだけどね。
https://codeforces.com/contest/1555/problem/F

問題

N頂点の無向グラフを考える。
初期状態で辺はない。
以下のクエリに順次答えよ。

  • 頂点U,V間に重さWの辺を追加できるか。
    • もし辺を追加しても、重さのxorが0となる(同じ点を2回以上通らない)閉路を作らないなら追加できる。

解法

まず重さを無視して、辺を追加しても閉路ができないならその辺は追加確定である。
こうして森を作ろう。

以下、閉路を作るクエリが何を起こすかを考える。
C(v) := 根からvまでの辺のxor
とする。

  • U-Vを結んだ段階で、U-LCA(U,V)-V-Uの閉路のxorが0となるなら不可。これは(C(U)^C(V)^W)が0ならダメである。
  • そうでない場合、U-LCA(U,V)-V-Uの閉路のxorが1となる。
    • 以後のクエリで、もしU'-V'間で辺を追加する場合、U'-LCA(U',V')-V'-U'の閉路と、U-LCA(U,V)-V-Uの閉路に共通となる辺があれば、U'-LCA(U',V')-V'-U'の閉路のxorが1でも、間U-LCA(U,V)-V-Uを通るすることでxorを0にできてしまう。よってダメである。

上記判定を行うには、パスU'-LCA(U',V)やV'-LCA(U',V')と、U-LCA(U,V)やV-LCA(U,V)に共通部分があるかを高速に判定できればよい。
これは、Heavy-Light Decompositionを使い、パス内の全辺に対し0/1を設定したり、パス中に1の辺を含むかを判定できればよい。

int N,Q;
int U[505050],V[505050],X[505050];

template<int um> class UF {
	public:
	vector<int> par,rank,cnt;
	UF() {par=rank=vector<int>(um,0); cnt=vector<int>(um,1); for(int i=0;i<um;i++) par[i]=i;}
	void reinit(int num=um) {int i; FOR(i,num) rank[i]=0,cnt[i]=1,par[i]=i;}
	int operator[](int x) {return (par[x]==x)?(x):(par[x] = operator[](par[x]));}
	int count(int x) { return cnt[operator[](x)];}
	int operator()(int x,int y) {
		if((x=operator[](x))==(y=operator[](y))) return x;
		cnt[y]=cnt[x]=cnt[x]+cnt[y];
		if(rank[x]>rank[y]) return par[x]=y;
		rank[x]+=rank[x]==rank[y]; return par[y]=x;
	}
};
UF<1<<21> uf;
int valid[1505050];
vector<pair<int,int>> E[1303030];
int C[1303030];

struct HLdecomp {
	static const int MD=20;
	int N,NE,id;
	vector<vector<int>> E;
	vector<int> D,S,B,C; // depth, size, base,heavy child
	
	vector<int> L,R,rev; // EulerTour
	vector<vector<int>> P,Cs; // parent for LCA,children
	void init(int N) { this->N=N, NE=0, E.clear(),E.resize(N); Cs.clear(),Cs.resize(N);
		D=S=B=C=L=R=rev=vector<int>(N,0); id=0; int i; P.clear(); FOR(i,MD+1) P.push_back(vector<int>(N,0));}
	void add_edge(int a,int b){ E[a].push_back(b),E[b].push_back(a); NE++;} // undir
	void dfs(int cur,int pre) { // get depth, parent, size, largest subtree
		int i;
		P[0][cur]=pre;S[cur]=1;C[cur]=-1;B[cur]=cur;
		D[cur]=(pre==cur)?0:(D[pre]+1);
		FOR(i,E[cur].size()) if(E[cur][i]!=pre) {
			int r=E[cur][i]; dfs(r,cur); S[cur]+=S[r];
			if(C[cur]==-1 || S[r]>S[C[cur]]) C[cur]=r;
		}
	}
	void dfs2(int cur,int pre) { // set base and list
		if(pre!=cur && C[pre]==cur) B[cur]=B[pre];
		else B[cur]=cur;
		Cs[B[cur]].push_back(cur);
		L[cur]=id++;
		rev[L[cur]]=cur;
		// DFS順を先行
		if(C[cur]!=-1) dfs2(C[cur],cur);
		FORR(r,E[cur]) if(r!=pre && r!=C[cur]) dfs2(r,cur);
		R[cur]=id;
	}
	pair<int,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 make_pair((aa==bb)?aa:P[0][aa], D[a]+D[b]-2*D[(aa==bb)?aa:P[0][aa]]);
	}
	void decomp(int root=0){
		assert(NE==N-1);
		dfs(root,root); dfs2(root,root);
		int i,x; FOR(i,MD) FOR(x,N) P[i+1][x]=P[i][P[i][x]];
	}
};

template<class V,int NV> class SegTree_3 {
public:
	vector<V> val, ma;
	SegTree_3(){
		int i;
		val.resize(NV*2,0); ma.resize(NV*2,0);
	};
	
	V getval(int x,int y,int l=0,int r=NV,int k=1) {
		if(r<=x || y<=l || y<=x) return 0;
		if(x<=l && r<=y) return ma[k];
		return val[k]+max(getval(x,y,l,(l+r)/2,k*2),getval(x,y,(l+r)/2,r,k*2+1));
	}
	
	void update(int x,int y, V v,int l=0,int r=NV,int k=1) {
		if(l>=r||y<=x) return;
		if(x<=l && r<=y) {
			val[k]+=v;
			ma[k]+=v;
		}
		else if(l < y && x < r) {
			update(x,y,v,l,(l+r)/2,k*2);
			update(x,y,v,(l+r)/2,r,k*2+1);
			ma[k]=val[k]+max(ma[k*2],ma[k*2+1]);
		}
	}
};

HLdecomp hl;
SegTree_3<int,1<<21> st;

void dfs(int cur,int pre,int v) {
	C[cur]=v;
	FORR2(e,x,E[cur]) if(e!=pre) dfs(e,cur,v^x);
}

void doset(int f,int t) {
	while(hl.B[f]!=hl.B[t]) {
		st.update(hl.L[hl.B[f]],hl.L[f]+1,1);
		f=hl.P[0][hl.B[f]];
	}
	st.update(hl.L[t]+1,hl.L[f]+1,1);
}
int get(int f,int t) { // fはtの子孫
	int ret = 0;
	while(hl.B[f]!=hl.B[t]) {
		ret = max(ret,st.getval(hl.L[hl.B[f]],hl.L[f]+1));
		f=hl.P[0][hl.B[f]];
	}
	ret = max(ret,st.getval(hl.L[t]+1,hl.L[f]+1));
	return ret;
}

void solve() {
	int i,j,k,l,r,x,y; string s;
	
	scanf("%d%d",&N,&Q);
	hl.init(N+1);
	FOR(i,Q) {
		scanf("%d%d%d",&U[i],&V[i],&X[i]);
		U[i]--;
		V[i]--;
		if(uf[U[i]]!=uf[V[i]]) {
			valid[i]=1;
			uf(U[i],V[i]);
			E[U[i]].push_back({V[i],X[i]});
			E[V[i]].push_back({U[i],X[i]});
			hl.add_edge(U[i],V[i]);
		}
	}
	FOR(i,N) if(uf[i]==i) {
		E[N].push_back({i,0});
		E[i].push_back({N,0});
		hl.add_edge(i,N);
	}
	dfs(N,N,0);
	hl.decomp(N);
	
	FOR(i,Q) {
		int u=U[i];
		int v=V[i];
		if(valid[i]==1) {
			_P("YES\n");
		}
		else if((C[u]^X[i])==C[v]) {
			_P("NO\n");
		}
		else {
			int lc=hl.lca(u,v).first;
			int ma=0;
			ma=max({get(u,lc),get(v,lc)});
			if(N==100000&&Q==500000&&i+1==3030) {
				_P("%d_%d_%d_%d_%d_%d___%d_%d\n",u,v,lc,hl.D[u],hl.D[v],hl.D[lc],get(u,lc),get(v,lc));
			}
			if(ma) {
				_P("NO\n");
			}
			else {
				doset(u,lc);
				doset(v,lc);
				_P("YES\n");
			}
		}
	}
	
}

まとめ

この問題、HL分解の手間はあるけど、考察自体はそこまで難しくないな…。