kmjp's blog

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

AtCoder ABC #437 (ユニークビジョンプログラミングコンテスト2025 クリスマス) : G - Colorful Christmas Tree

時間通り参加してれば結構いい順位だったか。
https://atcoder.jp/contests/abc437/tasks/abc437_g

問題

木を成すN点の無向グラフが与えられる。
また、初期状態で各点は赤緑青のいずれかで塗られており、その色が与えられる。

以下の手順を繰り返し、全辺を削除できるか。できるならその手順を求めよ。

  • 残った辺のうち、両端の頂点の色が異なる辺を1つ選び削除する。
  • その際、両端の点の色は赤→緑→青→赤と変化する。

解法

二部グラフのマッチングと考え、最大フローで解く。
各点の次数から、辺が消されるタイミングで赤・緑・青である状態が何回あるかは事前に計算できる。

元のグラフを二部グラフとみなしたとき、片方の点集合を左側、もう片方を右側と呼ぶようにする。
また、各点を3色の状態に合わせて3つに分けておく。
その際、以下のように辺を張る。
source →(容量は左側点の各色の選択回数)→左側の各点を3つに分けたもの→右側の各点を3つに分けたもの→(容量は右側点の各色の選択回数)→sink
ただし、「左側の各点を3つに分けたもの→右側の各点を3つに分けたもの」の部分は色が異なる点の間のみ容量1の辺を張る。

ここでフローを(N-1)流せれば解がある。
また、元々の各辺に対し、両端点がどの色の状態で削除されるかがわかるので、この情報をもとに削除順を復元すればよい。

int T,N;
int C[2020];
vector<int> E[2020];
int D[2020];
int U[2020],V[2020];
int CF[2020],CT[2020];

template<class V> class MaxFlow_dinic {
public:
	struct edge { int to,reve;V cap;};
	static const int MV = 202020;
	int NV=MV;
	vector<edge> E[MV];
	int itr[MV],lev[MV],mincut[MV]; //1ならsource側
	void init(int NV_) { int i; FOR(i,NV_) E[i].clear(); NV=NV_;}
	void add_edge(int x,int y,V cap,bool undir=false) {
		E[x].push_back((edge){y,(int)E[y].size(),cap});
		E[y].push_back((edge){x,(int)E[x].size()-1,undir?cap:0});
	}
	void bfs(int cur) {
		int i;
		FOR(i,NV) lev[i]=-1;
		queue<int> q;
		lev[cur]=0;
		q.push(cur);
		while(q.size()) {
			int v=q.front(); q.pop();
			FORR(e,E[v]) if(e.cap>0 && lev[e.to]<0) lev[e.to]=lev[v]+1, q.push(e.to);
		}
	}
	V dfs(int from,int to,V cf) {
		if(from==to) return cf;
		for(;itr[from]<E[from].size();itr[from]++) {
			edge* e=&E[from][itr[from]];
			if(e->cap>0 && lev[from]<lev[e->to]) {
				V f=dfs(e->to,to,min(cf,e->cap));
				if(f>0) {
					e->cap-=f;
					E[e->to][e->reve].cap += f;
					return f;
				}
			}
		}
		return 0;
	}
	V maxflow(int from, int to) {
		V fl=0,tf;
		while(1) {
			bfs(from);
			if(lev[to]<0) break;
			ZERO(itr);
			while((tf=dfs(from,to,numeric_limits<V>::max()))>0) fl+=tf;
		}
		//最小カット復元
		int i;
		FOR(i,NV) mincut[i]=0;
		queue<int> Q;
		mincut[from]=1;
		Q.push(from);
		while(Q.size()) {
			int cur=Q.front();
			Q.pop();
			FORR(e,E[cur]) if(e.cap>0&&mincut[e.to]==0) mincut[e.to]=1, Q.push(e.to);
		}
		return fl;
	}
};
MaxFlow_dinic<int> mf;

void dfs(int cur,int pre,int c) {
	D[cur]=c;
	FORR(e,E[cur]) if(e!=pre) dfs(e,cur,c^1);
}

void solve() {
	int i,j,k,l,r,x,y; string s;
	
	cin>>T;
	while(T--) {
		cin>>N>>s;
		FOR(i,N) {
			if(s[i]=='R') C[i]=0;
			if(s[i]=='G') C[i]=1;
			if(s[i]=='B') C[i]=2;
			E[i].clear();
		}
		FOR(i,N-1) {
			cin>>U[i]>>V[i];
			U[i]--;
			V[i]--;
			E[U[i]].push_back(V[i]);
			E[V[i]].push_back(U[i]);
		}
		dfs(0,0,0);
		mf.init(3*N+2);
		FOR(i,N) {
			int F[3]={};
			FOR(j,E[i].size()) F[(C[i]+j)%3]++;
			if(D[i]==0) {
				FOR(j,3) mf.add_edge(3*N,3*i+j,F[j]);
			}
			else {
				FOR(j,3) mf.add_edge(3*i+j,3*N+1,F[j]);
			}
		}
		map<pair<int,int>,int> M;
		FOR(i,N-1) {
			if(D[U[i]]==1) swap(U[i],V[i]);
			M[{U[i],V[i]}]=i;
			mf.add_edge(3*U[i]+0,3*V[i]+1,1);
			mf.add_edge(3*U[i]+0,3*V[i]+2,1);
			mf.add_edge(3*U[i]+1,3*V[i]+0,1);
			mf.add_edge(3*U[i]+1,3*V[i]+2,1);
			mf.add_edge(3*U[i]+2,3*V[i]+0,1);
			mf.add_edge(3*U[i]+2,3*V[i]+1,1);
		}
		int ret=mf.maxflow(3*N,3*N+1);
		if(ret==N-1) {
			cout<<"Yes"<<endl;
			FOR(i,N) if(D[i]==0) {
				FOR(j,3) {
					FORR(e,mf.E[i*3+j]) if(e.to<3*N&&e.cap==0) {
						x=e.to/3;
						y=e.to%3;
						k=M[{i,x}];
						CF[k]=j;
						CT[k]=y;
					}
				}
			}
			vector<int> ret;
			FOR(i,N-1) {
				FOR(j,N-1) if(U[j]>=0&&C[U[j]]==CF[j]&&C[V[j]]==CT[j]) {
					cout<<j+1<<" ";
					(C[U[j]]=C[U[j]]+1)%=3;
					(C[V[j]]=C[V[j]]+1)%=3;
					U[j]=-1;
					break;
				}
			}
			cout<<endl;
			
			
		}
		else {
			cout<<"No"<<endl;
		}
			
		
		
	}
}

まとめ

割とすんなりマッチング問題に持ち込めて良かったね。