読者です 読者をやめる 読者になる 読者になる

kmjp's blog

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

yukicoder : No.399 動的な領主

HLは思い浮かばなかった。
http://yukicoder.me/problems/no/399

問題

N頂点からなる木を成すグラフが与えられる。
各頂点には初期値0が設定されている。

ここでQ人のクエリが与えられる。
各クエリは2頂点(U,V)からなる。
この際U-Vの最短路上の各頂点の値は1加算される。また、(加算後の)各頂点の値の総和はコストとなる。
全クエリの合計コストを答えよ。
(各クエリにおいて、加算された値は繰り越される)

解法

x個のクエリで通過された頂点は、合計で1+2+...+x = x*(x-1)/2分のコストを計上する。
あとは各クエリにおいて各頂点がそれぞれ何回ずつ通過されるかを求めよう。

これにはLCAと累積和を用いて処理できる。
各クエリにおいて、W=LCA(U,V)とする。
U→W及びV→Wに至る経路上の頂点に1ずつ加算したいので、U,Vに1値を加算し、W及びparent(W)で1値を減算する。(Wを2重カウントしないようにこうする)
あとは葉から根に向けてimos法の要領で値の総和を取っていけば、各頂点の通過回数がわかる。

int N;
vector<int> E[101010];
int P[21][200005],D[200005];
ll tot[200005];
ll ret;

void dfs(int cur) {
	ITR(it,E[cur]) if(*it!=P[0][cur]) D[*it]=D[cur]+1, P[0][*it]=cur, dfs(*it);
}
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 (aa==bb)?aa:P[0][aa];               // vertex
}

void dfs2(int cur,int pre) {
	
	FORR(e,E[cur]) if(e!=pre) dfs2(e,cur);
	if(pre>=0) tot[pre] += tot[cur];
	ret += tot[cur]*(tot[cur]+1)/2;
}

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);
	FOR(i,19) FOR(x,N) P[i+1][x]=P[i][P[i][x]];
	
	cin>>i;
	while(i--) {
		cin>>x>>y;
		x--,y--;
		int lc=lca(x,y);
		tot[x]++;
		tot[y]++;
		tot[lc]--;
		if(lc!=0) tot[P[0][lc]]--;
	}
	dfs2(0,-1);
	cout<<ret<<endl;
	
}

まとめ

Link-Cut Tree勉強しようかなぁ…。

広告を非表示にする