kmjp's blog

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

yukicoder : No.1524 Upward Mobility

実装は面倒だけど方針はシンプルな問題。
https://yukicoder.me/problems/no/1524

問題

根付き木が与えられる。
各頂点vには2整数A[v]、B[v]が設定されている。

ここから、いくつかの頂点を無効化する。
この際、残された頂点について、親頂点vのA[v]より子頂点cのA[c]の方が大きくなるようにしたい。
条件を満たす範囲で、B[v]の総和の最大値を求めよ。

解法

f(v,x) := 頂点vのSubTree以下で考えるとき、x以上のA[v]を取るときのB[*]の総和の最大値
とする。
f(0,0)が解となるが、このf(v,x)を愚直に埋めるとO(N^2)かかってしまう。

g(v,x) := f(v,x-1)-f(v,x)と差分を取るようにする。
するとg(v,x)が非0となるのは、高々vのSubTreeの頂点数ぐらいである。
そこでg(v,x)をmapを使い値が非0となるところだけxとg(v,x)の値を持つように使用。

2つの子頂点c1,c2のf(c1,x)とf(c2,x)をマージしようとすると、両者の足し算とか必要で割と面倒。
しかし、g(c1,x)とg(c2,x)で考えるとマージテクで容易にくっ付けるだけで済む。

最後にg(0,*)の和を求めればよい。

int N;
int P[101010];
vector<int> E[101010];
int A[101010],B[101010];

map<int,int> M[202020];


void solve() {
	int i,j,k,l,r,x,y; string s;
	
	cin>>N;
	FOR(i,N-1) {
		cin>>P[i+1];
		E[P[i+1]-1].push_back(i+1);
	}
	FOR(i,N) cin>>A[i];
	FOR(i,N) cin>>B[i];
	
	for(i=N-1;i>=0;i--) {
		FORR(e,E[i]) {
			if(M[i].size()<M[e].size()) swap(M[i],M[e]);
			FORR2(a,b,M[e]) M[i][a]=b;
		}
		y=B[i];
		while(y) {
			auto it=M[i].lower_bound(A[i]);
			if(it==M[i].begin()) break;
			it--;
			x=min(y,it->second);
			it->second-=x;
			y-=x;
			if(it->second==0) M[i].erase(it);
		}
		M[i][A[i]]=B[i];
	}
	
	ll ret=0;
	FORR2(m,v,M[0]) ret+=v;
	
	cout<<ret<<endl;
	
}

まとめ

マージテクはともかく、差を管理するのに気付くのはちょっと難しい。