CF-613-D

发布时间 2024-01-13 21:46:08作者: fengxue-K

613-D 题目大意

给定一颗\(n\)个节点的树。

\(q\)组询问,每组询问给定\(k\)个点,问至少要删除树中多少个点才能使这\(k\)个点两两不连通,无解则输出\(-1\)

这里\(\sum{k_i}\)的规模大致和\(n\)相当。


Solution

虚树模板题。

暴力的做法是每组询问都对整棵树进行遍树形DP,复杂度为\(O(qn)\)。虚树的做法是对于每组询问的\(k\)个点,在原树的基础上重构一颗小规模的新树,在新树上进行树形DP,虚树能够保证重构树中的节点数量不超过\(2k\),时间复杂度为\(O(\sum{k_i}·log\sum{k_i})\)

对于一组点集,虚树的构建过程如下:
1、预处理出原树的\(dfs\)序,对点集按\(dfn\)序升序排序。
2、用栈\(st\)来维护\(dfs\)访问过的点,栈中的点从栈底到栈顶构成一条链,深度从小到大。首先加入根节点0。
3、现在考虑加入点\(x\),设\(y=st[top]\)\(z=lca(x,y)\),分情况讨论:
\(\;\;\;\;\)①当\(z=y\)时,说明\(x\)\(y\)子树内的节点,此时直接把\(x\)加入栈中即可。
\(\;\;\;\;\)②当\(z\ne y\)时,说明\(x\)不是\(y\)子树内的节点,此时应当把栈中深度大于\(z\)的节点全部出栈,在出栈的过程中进行连边建立虚树,直到\(dep[st[top-1]]<dep[z]\)为止。最后栈顶元素的深度仍然不小于\(z\)的深度,这时分两种情况\(\;\;\;\;\)讨论:
\(\;\;\;\;\;\;\;\;\)\(Ⅰ.\)如果\(y=z\),说明\(lca\)已在栈中,直接把\(x\)入栈即可。
\(\;\;\;\;\;\;\;\;\)\(Ⅱ.\)如果\(y\ne z\),说明\(lca\)不在栈中,那么把\(z\)\(y\)连一边条,再把\(y\)出栈,然后再把\(z\)\(x\)先后入栈。
4、枚举完点集之后,栈中仍然保存着一条链,把栈中节点全部出栈,并进行连边。

树形DP:
剩余的就是如何进行树形DP,首先判定无解的情况,存在一组父子点都在询问给的点集之中,此时无解。
对整棵树进行DP,维护一个\(sz\)值,询问点为\(1\),其余点为\(0\),分情况讨论:
1、如果当前点\(x\)是询问点,对它的所有儿子\(y\)进行\(dfs\),如果儿子\(y\)\(sz\)值为正,说明要从\(x\)\(y\)的路径上删除一个点,否则不需要操作。
2、如果当前点\(x\)不是询问点,对它的所有儿子\(y\)进行\(dfs\),并把所有儿子的\(sz\)值累加到\(x\)\(sz\)值中,如果最终\(sz[x]\)超过1,说明删除这个非询问点,能够隔离开它子树中所有的询问点,否则不需要操作。

要注意的是需要在递归结束时清空相应的\(sz\)值,以及把新加的边都删去。如果整个操作完再去统一初始化,则时间复杂度会退化到\(O(qn)\)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

int main(){
	ios_base::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	int n;
	cin>>n;
	int P=19;
	vector<vector<int>> e(n),ne(n);
	vector<vector<int>> fa(n,vector<int>(P));
	vector<int> dfn(n),dep(n);
	int idx=0;
	for(int i=1;i<n;i++){
		int x,y;
		cin>>x>>y;
		--x,--y;
		e[x].push_back(y);
		e[y].push_back(x);
	}
	function<void(int,int,int)> dfs=[&](int x,int father,int depth){
		dfn[x]=++idx,fa[x][0]=father,dep[x]=depth;
		for(int i=1;i<P;i++) fa[x][i]=fa[fa[x][i-1]][i-1];
		for(auto y:e[x]){
			if(y==father) continue;
			dfs(y,x,depth+1);
		}
	};
	function<int(int,int)> lca=[&](int x,int y){
		if(dep[x]<dep[y]) swap(x,y);
		for(int i=P-1;~i;i--){
			if(dep[fa[x][i]]>=dep[y]) x=fa[x][i];
		}
		if(x==y) return x;
		for(int i=P-1;~i;i--){
			if(fa[x][i]!=fa[y][i]){
				x=fa[x][i],y=fa[y][i];
			}
		}
		return fa[x][0];
	};
	dfs(0,0,0);
	int q;
	cin>>q;
	vector<int> sz(n);
	int ans=0;
	function<void(vector<int>&)> build=[&](vector<int> &p){
		sort(p.begin(),p.end(),[&](const auto &x,const auto &y){
			return dfn[x]<dfn[y];
		});
		vector<int> st{0};//根节点入栈
		if(p[0]!=0) st.push_back(p[0]);//第一个查询点入栈
		for(int i=1;i<p.size();i++){
			int x=p[i],y=st.back();
			st.pop_back();
			int z=lca(x,y);
			while(st.size()&&dep[st.back()]>=dep[z]){//对当前链连边
				ne[st.back()].push_back(y);
				y=st.back();
				st.pop_back();
			}
			if(z!=y){//对lca和栈顶点连边,栈顶点出栈,lca入栈
				ne[z].push_back(y);
			}
			st.push_back(z);
			st.push_back(p[i]);
		}
		while(st.size()>1){///对最后一条链连边
			int y=st.back();
			st.pop_back();
			ne[st.back()].push_back(y);
		}
	};
	function<void(int)> DP=[&](int x){
		if(sz[x]){
			for(auto y:ne[x]){
				DP(y);
				if(sz[y]) ans++,sz[y]=0;
			}
		}else{
			for(auto y:ne[x]){
				DP(y);
				sz[x]+=sz[y],sz[y]=0;
			}
			if(sz[x]>1) ans++,sz[x]=0;
		}
		ne[x].clear();
	};
	while(q--){
		int k;
		cin>>k;
		vector<int> p(k);
		for(int i=0;i<k;i++){
			cin>>p[i];
			p[i]--;
			sz[p[i]]=1;
		}
		int flag=0;
		for(int i=0;i<k;i++){
			if(p[i]!=0&&sz[fa[p[i]][0]]) flag=1;
		}
		if(flag){
			for(int i=0;i<k;i++) sz[p[i]]=0;
			cout<<-1<<'\n';
			continue;
		}
		build(p);
		ans=0;
		DP(0);
		sz[0]=0;
		cout<<ans<<'\n';
	}
	return 0;
}