【虛樹dp】 CF613D Kingdom and its Cities
阿新 • • 發佈:2021-07-07
樹形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; }