kmjp's blog

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

Codeforces #177 Div1 D. Polo the Penguin and Trees

これも何とか自力回答。
http://codeforces.com/contest/288/problem/D

問題

1~Nの数字が振られた頂点からなる木が与えられる。
1~Nのうちから、a<b、c<dとなる4つの数字を選んだ時、a番とb番の頂点の最短路と、c番とd番の頂点の最短路が同じ頂点を通らないような(a,b,c,d)の組は何通りあるか。

解法

aとb、cとdの大小関係は置いておいて、2つのパスが重ならないように取る取り方を考えればよい。

まず1回DFSで、各頂点において各辺の先に何頂点あるか求めておく。

次に各頂点xをa-bの一部の候補として、以下のケースが何通りあるか答える。

  • xがa-bの端である場合、xの辺を1個選択するとその先の頂点のいずれかがもう1端であり、その組みわせはその辺の先の頂点数となる。
    • さらに、もう1辺を選び、その辺の先でc-dのパスを構築する。これは辺の先にある頂点群から2つ選ぶ選び方と一致する。
  • xがa-bの端でない場合、xの辺を2個選択するとそれぞれの先の頂点のいずれかがもう1端であり、その組みわせはその辺の先の頂点数となる。
    • 上記同様さらに、もう1辺を選び、その辺の先でc-dのパスを構築する。これは辺の先にある頂点群から2つ選ぶ選び方と一致する。

上記処理だと、xを選択し、a-bの端となる辺を2つ選択し、c-dのパスを構築する辺を選択するので最悪O(N^4)かかり間に合わない。
うまく累積和を使うと、全体でO(N)で済むようになる。

int N;
vector<ll> E[100000],D[100000];

int dfs(int cur,int pre) {
	int i,j=-1,k=N-1;
	D[cur].resize(E[cur].size());
	FOR(i,E[cur].size()) {
		int tar=E[cur][i];
		if(tar==pre) j=i;
		else k-=D[cur][i]=dfs(tar,cur);
	}
	if(j>=0) D[cur][j]=k;
	return N-k;

}

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);
	}
	dfs(0,-1);
	ll ret=0;
	FOR(i,N) {
		ll tot1=0,tot2=0,tot5=0;
		FOR(j,D[i].size()) tot1+=D[i][j],tot2+=D[i][j]*(D[i][j]-1)/2, tot5+=D[i][j]*D[i][j]*(D[i][j]-1)/2;
		FOR(j,D[i].size()) ret+=D[i][j]*(tot2-D[i][j]*(D[i][j]-1)/2);
		ll tot3=tot1, tot4=tot1;
		FOR(j,D[i].size()) tot3-=D[i][j], ret+=D[i][j]*tot3*tot2;
		FOR(j,D[i].size()) tot4-=D[i][j],  ret-=D[i][j]*tot4*(D[i][j]*(D[i][j]-1)/2);
		FOR(j,D[i].size()) tot5-=D[i][j]*D[i][j]*(D[i][j]-1)/2, ret-=D[i][j]*tot5;
	}
	
	cout << ret << endl;
}

まとめ

なんかこれは割とすぐに思いついた。
むしろ累積和の取り方に苦戦。