1. 程式人生 > 其它 >【虛樹dp】 CF613D Kingdom and its Cities

【虛樹dp】 CF613D Kingdom and its Cities

題鏈

樹形dp

令f[u]表示以u為根的子樹需要的最小點數;
令g[u]表示以u為根的子樹未被截斷的點數;

對於一個點u,其孩子節點v;
 f[u] = \(\sum_{}\)f[v];
 g[u] = \(\sum_{}\)g[v];
若u是關鍵點:
 則需要截斷子樹中未被截斷的點:
  f[u] += g[u];
  g[u] = 1;
若u不是關鍵點:
 若子樹中未被截斷的點>1,則點u需要放置:
  f[u] += 1;
  g[u] = 0;
 若子樹中未被截斷的點<=1,不需要改動;

優化

由於所給資料的範圍對k是有限制的,所以對給出的關鍵點構造虛樹,然後再dp;

程式碼lca部分採用樹剖;

#include <bits/stdc++.h>
using namespace std;
#define LL long long
#define ls rt<<1
#define rs rt<<1|1
#define MAXN 1e9
#define MS 100009
#define mod 998244353

int n,m,k;
// 建樹和樹剖所用陣列----------------- 
vector<int > vc[MS];
int dep[MS],fa[MS],zson[MS],sz[MS];
int top[MS],val[MS],dfn[MS],tot;
// ----------------------------------- 
int p[MS],mark[MS];
int sta[MS],stp;
int tmp[MS],cnt;
vector<int > vx[MS];
int ff[MS],gg[MS];

void dfs1(int u,int f){
	sz[u] = 1;
	dep[u] = dep[f]+1;
	fa[u] = f;
	zson[u] = 0;
	int maxnzson = 0;
	for(auto &v:vc[u]){
		if(v != f){
			dfs1(v,u);
			sz[u] += sz[v];
			if(sz[v] > maxnzson){
				maxnzson = sz[v];
				zson[u] = v;
			}
		}
	}
}

void dfs2(int u,int tp){
	top[u] = tp;
	dfn[u] = ++tot;
	val[tot] = 0;
	if(zson[u]) dfs2(zson[u],tp);
	for(auto &v:vc[u]){
		if(v != fa[u] && v != zson[u]){
			dfs2(v,v);
		}
	}
}

int __lca(int u,int v){
	while(top[u] != top[v]){
		if(dep[ top[u] ] < dep[ top[v] ]) swap(u,v);
		u = fa[top[u]];
	}
	if(dep[u] < dep[v]) return u;
	return v;
}

bool cmp(int t1,int t2){
	return dfn[t1] < dfn[t2];
}

void insert(int x){ 	// 插入點x 
	if(x == 1) return; 	// 根節點 
	if(stp == 1){ 		// 棧中元素只有一個 
		sta[++stp] = x;
		return;
	}
	int lca = __lca(x,sta[stp]);
	if(lca == sta[stp]){ // 棧頂元素是x的父親 
		sta[++stp] = x;
		return;
	}
	// 依次彈出棧中元素,使得dfn[棧頂] <= dfn[x] <= dfn[棧次頂]
	while(stp>1 && dfn[lca] <= dfn[sta[stp-1]]){
		int u = sta[stp-1];
		int v = sta[stp];
		tmp[++cnt] = v;
		vx[u].push_back(v);
		vx[v].push_back(u);
		stp--;
	}
	// 判斷棧頂是不是x的父親 
	if(lca != sta[stp]){
		int u = lca;
		int v = sta[stp];
		tmp[++cnt] = v;
		vx[u].push_back(v);
		vx[v].push_back(u);
		sta[stp] = lca;
	}
	sta[++stp] = x;
}

void build(){
	stp = 1;
	sta[stp] = 1; // 構建虛樹所用棧,此棧儲存的數永遠是樹上同一條鏈 
	cnt = 1;
	tmp[cnt] = 1; // tmp陣列儲存虛樹所用節點,方便清理 
	for(int i=1;i<=k;i++){
		insert(p[i]);
	}
	while(stp>1){
		int u = sta[stp-1];
		int v = sta[stp];
		tmp[++cnt] = v;
		vx[u].push_back(v);
		vx[v].push_back(u);
		stp--;
	}
}

void cal(int u,int f){
	for(auto &v:vx[u]){
		if(v == f) continue;
		cal(v,u);
		ff[u] += ff[v];
		gg[u] += gg[v];
	}
	if(mark[u]){
		ff[u] += gg[u];
		gg[u] = 1;
	}
	else{
		if(gg[u] > 1){
			ff[u] += 1;
			gg[u] = 0;
		}
		else{
			;
		}
	}
}

void clean(){
	for(int i=1;i<=cnt;i++){
		vx[tmp[i]].clear();
		ff[tmp[i]] = gg[tmp[i]] = 0;
	}
	for(int i=1;i<=k;i++){
		mark[p[i]] = 0;
	}
}

int main() {
	ios::sync_with_stdio(false);
	cin >> n;
	for(int i=1;i<=n-1;i++){
		int u,v;
		cin >> u >> v;
		vc[u].push_back(v);
		vc[v].push_back(u);
	}
	// 樹剖 
	dfs1(1,1);
	dfs2(1,1);
	
	cin >> m;
	while(m--){
		cin >> k;
		for(int i=1;i<=k;i++){
			cin >> p[i];
			mark[p[i]] = 1; // 標記關鍵點 
		} 
		// -1的情況特判 
		int flag = 1;
		for(int i=1;i<=k;i++){
			if(mark[ fa[p[i]] ] && fa[p[i]] != p[i]){
				flag = 0;
				break;
			}
		}
		if(!flag){
			cout << -1 << "\n";
		}
		else{
			// 所給關鍵點按dfs序排序 
			sort(p+1,p+k+1,cmp); 
			// 構建虛樹 
			build();
			// 樹形dp 
			cal(1,1);
			cout << ff[1] << "\n";
		}
		// 清空所用陣列 
		clean();
	}
	

	return 0;
}