「虛樹」學習筆記
虛樹
虛樹的定義
虛樹:將樹上有用的節點建立新的圖,而捨去關鍵節點之間的沒有用處的節點
虛樹的用途:對於一些有關鍵點的圖而言,其餘沒有用處的節點在操作的時候會作出很多的冗餘操作,時間效率大大降低,而利用虛樹建圖就可以捨去沒有用的操作
前置知識1:\(dfs\)序
\(dfs4序,顧名思義,就是在對圖做\)dfs\(的時候的順序。 舉個例子: ![](https://img2020.cnblogs.com/blog/1999076/202010/1999076-20201007162945768-640298406.png) 該圖中節點就是按\)dfs\(序編號的 我們可以利用\)dfs\(序找到一些很有用的性質: 1.\)dfs\(序較大的有兩種情況,一種是\)
來道例題(\(CF613D\; Kingdom \; and\; its \;Cities\))
題意:給定一棵樹, \(q\) 組詢問,每組詢問給定 \(k\) 個點,你可以刪掉不同於那 \(k\) 個點的 \(m\) 個點,使得這 \(k\) 個點兩兩不連通,要求最小化 \(m\),如果不可能輸出 −1。詢問之間獨立。
思路:
首先如果兩個節點都是關鍵點,並且兩個點相鄰,那麼就是無解的情況,否則都有解。那麼怎麼求最小的 \(m\)
一種方法可以暴力遍歷全圖,兩個節點之間只斷一個點,選擇那種可以切掉一個點可以將多個點都斷開連線的,比如這種:
我們只把1節點刪去就可以達到所有點都不聯通的目的。
暴力做的話,時間複雜度並不是很優秀。我們考慮只用關鍵點和一些必要的公共祖先去建樹,那麼虛樹的關鍵就在於如何去利用 \(dfs\) 序建圖。
首先對於關鍵點用 \(dfs\) 序排序,如果根節點不是關鍵點,把根節點也加進去。
當棧為空或棧中只有一個元素(即 \(top\) <=1, \(top\) 從0開始),直接把x壓入棧中
維護一個棧,顯然 \(dfs\) 序小的節點先進棧,記住, \(dfs\) 序小的在棧底。
如果 \(dfs\)
否則該節點就是在新的子樹中,是這種情況:
判斷依據就是看將當前點和棧頂的 \(lca\) 是不是棧頂元素,也就是圖中當前節點9和棧頂節點8的 \(lca\) 是不是8,如果是,那麼就直接推進棧裡;
不是的話,說明 \(x\) 和 \(stk[top]\) 分屬 \(lca\) 的兩棵不同的子樹,而且\(stk[top]\)所在的子樹中已經構建完成了。所以我們把 \(lca\) 的 \(stk[top]\) 所在的子樹彈棧,在彈棧的過程中建邊,直到 \(dfn[stk[top]]<=dfn[lca]<=dfn[stk[top-1]]\)(即\(lca\)在棧頂的兩元素的路徑上),或者棧內的元素小於兩個,可以自己模擬一下。
此時我們看\(lca\)是不是棧頂元素,如果是的話,將當前節點進棧,如果不是的話,從棧頂向\(lca\)連邊,彈出棧頂,將\(lca\)壓進棧,並將當前節點也進棧。
在列舉完關鍵點後,將棧內剩餘元素都建邊,彈棧。此時虛樹已經建好了,就可以用之前的做法在虛樹上操作了。
建圖程式碼(細品):
inline void ins(int x){
if (tp == 0){
stk[tp = 1] = x;
return;
}
int LCA = lca(stk[tp], x);
while ((tp > 1) && (deep[LCA] < deep[stk[tp - 1]])) {
addedge(stk[tp - 1], stk[tp]);
--tp;
}
if (deep[LCA] < deep[stk[tp]]) addedge(LCA, stk[tp--]);
if ((!tp) || (stk[tp] != LCA)) stk[++tp] = LCA;
stk[++tp] = x;
}
大體程式碼實現:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 1e5 + 50;
inline int read () {
int x = 0, f = 1; char ch = getchar();
for (;!isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) x = x * 10 + ch - '0';
return x * f;
}
int n, m, q;
struct Edge {
int to, next;
} edge[maxn << 1];
int tot, head[maxn];
void addedge (int a, int b) {
edge[++tot].to = b;
edge[tot].next = head[a];
head[a] = tot;
}
int siz[maxn], fa[maxn], deep[maxn], son[maxn];
void dfs1 (int u) {
siz[u] = 1;
for (register int i = head[u]; i; i = edge[i].next) {
int v = edge[i].to;
if (v == fa[u]) continue;
deep[v] = deep[u] + 1;
fa[v] = u;
dfs1 (v);
siz[u] += siz[v];
if (siz[v] > siz[son[u]]) son[u] = v;
}
}
int dfn_clock;
int dfn[maxn], top[maxn];
void dfs2 (int u) {
dfn[u] = ++dfn_clock;
if (son[u]) {
top[son[u]] = top[u];
dfs2 (son[u]);
for (register int i = head[u]; i; i = edge[i].next) {
int v = edge[i].to;
if (v != fa[u] && v != son[u]) {
top[v] = v;
dfs2 (v);
}
}
}
}
inline int lca (int x, int y) {
while (top[x] != top[y]) {
if (deep[top[x]] > deep[top[y]]) {
x = fa[top[x]];
} else {
y = fa[top[y]];
}
}
if (deep[x] < deep[y]) {
return x;
} else {
return y;
}
}
int tp;
int stk[maxn];
inline void ins(int x){
if (tp == 0){
stk[tp = 1] = x;
return;
}
int LCA = lca(stk[tp], x);
while ((tp > 1) && (deep[LCA] < deep[stk[tp - 1]])) {
addedge(stk[tp - 1], stk[tp]);
--tp;
}
if (deep[LCA] < deep[stk[tp]]) addedge(LCA, stk[tp--]);
if ((!tp) || (stk[tp] != LCA)) stk[++tp] = LCA;
stk[++tp] = x;
}
int ans;
int a[maxn];
bool cmp (int a, int b) {
return dfn[a] < dfn[b];
}
void dfs3 (int u) {
int x;
if (siz[u]) {
for (register int i = head[u]; i; i = edge[i].next) {
int v = edge[i].to;
dfs3 (v);
if (siz[v]) {
siz[v] = 0;
ans++;
}
}
} else {
for (register int i = head[u]; i; i = edge[i].next) {
int v = edge[i].to;
dfs3 (v);
siz[u] += siz[v];
siz[v] = 0;
}
if (siz[u] > 1) {
ans++;
siz[u] = 0;
}
}
}
int main () {
n = read();
int from, to;
for (register int i = 1; i < n; i++) {
from = read(), to = read();
addedge (from, to), addedge(to, from);
}
tot = 0;
top[1] = 1;
deep[1] = 1;
dfs1 (1);
dfs2 (1);
memset (head, 0, sizeof head);
memset (siz, 0, sizeof siz);
tot = 0;
q = read();
while (q--) {
m = read();
for (register int i = 1; i <= m; i++) {
a[i] = read();
siz[a[i]] = 1;
}
bool judge = false;
for (register int i = 1; i <= m; i++) {
if (siz[fa[a[i]]]) {
puts("-1");
judge = true;
break;
}
}
if (judge == true) {
memset (siz, 0, sizeof siz);
continue;
}
ans = 0;
sort (a + 1, a + 1 + m, cmp);
if (a[1] != 1) {
stk[tp = 1] = 1;
}
for (register int i = 1; i <= m; i++) {
ins(a[i]);
}
if (tp) {
while (--tp) {
addedge (stk[tp], stk[tp + 1]);
}
}
dfs3 (1);
memset (siz, 0, sizeof siz);
dfn_clock = 0;
cout<<ans<<endl;
}
return 0;
}
例題2(涼宮春日的消失)
在觀察涼宮和你相處的過程中,\(Yoki\)產生了一個叫做愛的\(bugfeature\),將自己變成了一個沒有特殊能力的普通女孩並和你相遇。但你仍然不能扔下涼宮,準備利用\(Yoki\)留下的緊急逃脫程式回到原來的世界。這個緊急逃脫程式的關鍵就是將線索配對。
為了簡化問題,我們將可能的線索間的關係用一棵\(n\)個點的樹表示,兩個線索的距離定義為其在樹上唯一最短路徑的長度。因為你不知道具體的線索是什麼,你需要進行\(q\)次嘗試,每次嘗試都會選中一個大小為偶數的線索集合\(V\) ,你需要將線索兩兩配對,使得配對線索的距離之和不超過\(n\) 。如果這樣的方案不存在,輸出\(No\) 。
思路
一眼看到選關鍵點,顯然可以用虛樹搞,並且很顯然有一個性質,該條件只要關鍵點數是偶數,那麼一定存在方案。一個類似貪心的思想,可以在一顆子樹中找到配對的就在一顆子樹中解決,並且一顆子樹中最多隻會有一個點沒有找到配對,那麼把當前點扔到父節點中找配對,並且這個點選最靠上的,具體證明不證了,畫畫圖很顯然。
然後每次把關鍵點建一顆虛樹,然後進行上述操作搞搞就好了。
程式碼實現(為啥我的跑的這麼慢\(qwq\))
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
const int maxn = 2e5 + 50;
inline int read () {
int x = 0, f = 1; char ch = getchar();
for (;!isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) x = x * 10 + ch - '0';
return x * f;
}
int n;
struct Edge {
int from, to, next;
} edge[maxn << 1];
int tot, head[maxn];
inline void addedge (int a, int b) {
edge[++tot].to = b;
edge[tot].from = a;
edge[tot].next = head[a];
head[a] = tot;
}
deque<int> que[maxn];
bool col[maxn];
int f[maxn];
int son[maxn], siz[maxn], deep[maxn];
void dfs1 (int u) {
siz[u] = 1;
for (register int i = head[u]; i; i = edge[i].next) {
int v = edge[i].to;
if (v == f[u]) continue;
f[v] = u;
deep[v] = deep[u] + 1;
dfs1 (v);
siz[u] += siz[v];
if (siz[v] > siz[son[u]]) son[u] = v;
}
}
int dfn[maxn], dfn_clock;
int top[maxn];
void dfs2 (int u) {
dfn[u] = ++dfn_clock;
if (son[u]) {
top[son[u]] = top[u];
dfs2 (son[u]);
for (register int i = head[u]; i; i = edge[i].next) {
int v = edge[i].to;
if (v != f[u] && v != son[u]) {
top[v] = v;
dfs2 (v);
}
}
}
}
inline int lca (int x, int y) {
while (top[x] != top[y]) {
if (deep[top[x]] > deep[top[y]]) {
x = f[top[x]];
} else {
y = f[top[y]];
}
}
if (deep[x] < deep[y]) return x;
return y;
}
int tp;
int stk[maxn];
inline void ins(int x)
{
if (tp == 0)
{
stk[tp = 1] = x;
return;
}
int ance = lca(stk[tp], x);
while ((tp > 1) && (deep[ance] < deep[stk[tp - 1]]))
{
addedge(stk[tp - 1], stk[tp]);
--tp;
}
if (deep[ance] < deep[stk[tp]]) addedge(ance, stk[tp--]);
if ((!tp) || (stk[tp] != ance)) stk[++tp] = ance;
stk[++tp] = x;
}
int a[maxn];
inline void dfs (int u) {
for (register int i = head[u]; i; i = edge[i].next) {
int v = edge[i].to;
dfs(v);
if (!que[v].empty()) {
int a = que[v].front();
que[v].pop_front();
que[u].push_back(a);
}
}
if (col[u]) que[u].push_front(u);
while (!que[u].empty()) {
int a = que[u].back();
que[u].pop_back();
if (!que[u].empty()) {
int b = que[u].back();
que[u].pop_back();
printf("%d %d\n", a, b);
} else {
que[u].push_back(a);
break;
}
}
}
bool cmp (int a, int b) {
return dfn[a] < dfn[b];
}
int main () {
n = read();
int x, y;
for (register int i = 1; i < n; i++) {
x = read(), y = read();
addedge (x, y), addedge (y, x);
}
int s;
dfs1 (1);
dfs2 (1);
memset (head, 0, sizeof head);
tot = 0;
while (1) {
s = read();
if (s == 0) return 0;
for (register int i = 1; i <= s; i += 1) {
a[i] = read();
col[a[i]] = true;
}
printf("Yes\n");
sort (a + 1, a + 1 + s, cmp);
if (a[1] != 1) {
stk[tp = 1] = 1;
}
for (register int i = 1; i <= s; i++) {
ins (a[i]);
}
if (tp) {
while (--tp) {
addedge (stk[tp], stk[tp + 1]);
}
}
dfs (1);
memset(col, 0, sizeof col);
memset (head, 0, sizeof head);
tot = 0;
}
return 0;
}
完結散花\(qwq\)