kmjp's blog

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

yukicoder : No.1976 Cut then Connect

なるほど。
https://yukicoder.me/problems/no/1976

問題

木を成す無向グラフが与えられる。
このグラフの任意の無向辺を1つ削除し、その後2つの連結成分を1つに連結できるように1本辺を任意の位置に追加できるとする。
その後の直径の最大値を求めよ。

解法

証明はEditorialに譲るとして、2つの連結成分の直径をX,Yとすると、解はmax(X,Y,floor((X+1)/2)+floor((Y+1)/2)+1)となる。
あとは、各辺を削除した場合の両連結成分の直径を求められれば、あとは削除する辺を総当たりしながら上記式の最大値を取るだけである。
この両連結成分の直径は、全方位木DPで求めて行こう。

int N;
vector<int> E[202020];

pair<int,int> farthest(int cur,int pre,int d,vector<int>& D) {
	D[cur]=d;
	pair<int,int> r={d,cur};
	FORR(e,E[cur]) if(e!=pre) r=max(r, farthest(e,cur,d+1,D));
	return r;
}

pair<int,vector<int>> diameter() { // diameter,center
	vector<int> D[2];
	D[0].resize(N);
	D[1].resize(N);
	auto v1=farthest(0,0,0,D[0]);
	auto v2=farthest(v1.second,v1.second,0,D[0]);
	v1=farthest(v2.second,v2.second,0,D[1]);
	pair<int,vector<int>> R;
	R.first = v2.first;
	//直径を取る場合
	R.second.resize(v2.first+1);
	for(int i=N-1;i>=0;i--) if(D[0][i]+D[1][i]==R.first) R.second[D[0][i]]=i;
	return R;
}

int id[202020];
int ma[2][202020];
int mac[2][202020];

int dfs(int cur,int pre,int id) {
	vector<int> cand={0,0};
	FORR(e,E[cur]) if(e!=pre) {
		cand.push_back(dfs(e,cur,id)+1);
		ma[id][cur]=max(ma[id][cur],ma[id][e]);
	}
	sort(ALL(cand));
	reverse(ALL(cand));
	ma[id][cur]=max(ma[id][cur],cand[0]+cand[1]);
	mac[id][cur]=cand[0];
	return cand[0];
}

void solve() {
	int i,j,k,l,r,x,y; string s;
	
	cin>>N;
	FOR(i,N-1) {
		cin>>x>>y;
		E[x-1].push_back(y-1);
		E[y-1].push_back(x-1);
	}
	auto d=diameter();
	MINUS(id);
	FOR(i,d.second.size()) id[d.second[i]]=i;
	dfs(d.second[0],d.second[0],0);
	dfs(d.second.back(),d.second.back(),1);
	
	int mi=d.first;
	FOR(i,d.second.size()-1) {
		x=ma[1][d.second[i]];
		y=ma[0][d.second[i+1]];
		k=max({1+(x+1)/2+(y+1)/2,x,y});
		mi=min(mi,k);
	}
	cout<<mi<<endl;
	
}

まとめ

max(X,Y,floor((X+1)/2)+floor((Y+1)/2)+1)は直感的に正しそうというのはわかるが、実際正しいというのは覚えておいてもいいかも。