kmjp's blog

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

yukicoder : No.899 γatheree

5問目まではサクサク解けたので、参加しておけばよかったな。
https://yukicoder.me/problems/no/899

問題

木を成す無向グラフが与えられる。
各頂点には初期状態で何匹かの妖精がおり、その数が入力として与えられる。

以下のクエリに順次答えよ。

  • 頂点番号vが指定される。そこから距離2以下の頂点にいる妖精が、皆vに集まる。その際vにいる妖精数を答えよ。なお、移動した妖精の状態は、以降のクエリにも残る。

解法

木を根付き木とみなし、BFS順でEuler Tourしておこう。
加えて、各頂点に対し、そこから1回で移動できる頂点に関する情報(親頂点と、子頂点)と2回で移動できる頂点の情報(兄弟の頂点と孫頂点と親の親頂点)を考える。
BFS順でEulerTourすると、それぞれ区間で表現できる。
setに妖精数が正の頂点に関し(位置,妖精数)のpairを載せると、区間内の妖精を高速に集めることができる。

int N,Q;
vector<int> E[101010];
int D[101010],P[101010];
int CL[101010],CR[101010],GL[101010],GR[101010];
int DN[101010],id[101010];
set<pair<int,ll>> S[101010];

void dfs(int cur,int pre,int d) {
	D[cur]=d;
	P[cur]=pre;
	id[cur]=++DN[D[cur]];
	CL[cur]=GL[cur]=1<<20;
	CR[cur]=GR[cur]=-1;
	FORR(e,E[cur]) if(e!=pre) {
		dfs(e,cur,d+1);
		CL[cur]=min(CL[cur],id[e]);
		CR[cur]=max(CR[cur],id[e]);
		GL[cur]=min(GL[cur],CL[e]);
		GR[cur]=max(GR[cur],CR[e]);
	}
	
}

ll poke(int e) {
	ll tot=0;
	auto it=S[D[e]].lower_bound(make_pair(id[e],0));
	if(it!=S[D[e]].end() && it->first==id[e]) {
		tot=it->second;
		S[D[e]].erase(it);
	}
	return tot;
}

ll poke_range(int d,int L,int R) {
	ll tot=0;
	while(1) {
		auto it=S[d].lower_bound({L,0});
		if(it==S[d].end() || it->first>R) break;
		tot+=it->second;
		S[d].erase(it);
	}
	return tot;
}


void solve() {
	int i,j,k,l,r,x,y; string s;
	
	cin>>N;
	FOR(i,N-1) {
		cin>>x>>y;
		E[x].push_back(y);
		E[y].push_back(x);
	}
	dfs(0,0,0);
	FOR(i,N) {
		cin>>x;
		S[D[i]].insert({id[i],x});
	}
	cin>>Q;
	while(Q--) {
		cin>>x;
		ll tot=poke(x);
		
		if(D[x]>=2) {
			tot+=poke(P[P[x]]);
		}
		if(D[x]>=1) {
			tot+=poke(P[x]);
			tot+=poke_range(D[x],CL[P[x]],CR[P[x]]);
		}
		tot+=poke_range(D[x]+1,CL[x],CR[x]);
		tot+=poke_range(D[x]+2,GL[x],GR[x]);
		S[D[x]].insert({id[x],tot});
		cout<<tot<<endl;
	}
}

まとめ

なんとなくζって6番目のイメージわかないんだよな。