虛樹入門


鬼知道為什么我又要開這個新坑,就挺離譜的。

虛樹這東西是在訂正模擬賽題目時遇到的,正解需要這個東西,但是我不會...

然后去學了一下感覺這個東西本身也不是很難,當然需要結合着題目來講,就會很容易懂了。

我們引入一道例題,並配合題目進行講解。

CF613D Kingdom and its Cities

有一顆 \(n\) 個節點的樹,一些節點設為了重要節點。
重要節點是不會被選中的。重要節點之外的節點就是非重要節點,選到這些節點之后,與之相連的邊就會斷掉,求能否選擇一些節點讓重要節點兩兩不連通。

重要節點會選擇 \(m\) 次,每次選擇 \(k\) 個節點。

\(\sum k \leq 10^5\) , \(n\leq 10^5\)


首先考慮不行的情況,如果有兩個重要節點之間是直接連邊的,那么就是不行的,否則都是行的。

考慮怎么轉移做這個東西,分類討論一下。

\(u\) 為關鍵節點的時候,將每個沒有斷開的子孫全部斷開就是了。

\(u\) 不為關鍵節點時,如果他有多個關鍵節點都沒有斷開,那么就將他自己斷開就是了。

這么做的話每次都要遍歷一遍全樹,這讓我們的復雜度變得不可接受,為 \(O(nm)\)

考慮到其實對於選擇的點不同有些點是可以不用選上的,我們是否可以將這些點給刪去?

當然可以。

我們考慮哪些節點是無所謂的,發現每次其實只會更你重要節點的 \(\text{LCA}\) 有關,那么你就只需要保留的是這些節點以及它們的 \(\text{LCA}\) 就行了。

這么刪去無關節點之后形成的樹就是虛樹。

虛樹建立出來之后就會讓你的復雜度變小這是顯而易見的。

然后我的虛樹的建立可能比較不一樣,這是從神仙 \(\color{red}{\text{p}}\color{black}{\text{_b_p_b}}\) 那里學到的。

把點按dfn排序,相鄰點的lca丟進去,再排序,每個點在虛樹上的父親就是自己和前驅的lca。

很妙,這個東西的證明就不證明了吧,聯合 \(dfn\) 序想一下就知道這是正確的。

然后這個東西還很好寫,這樣做你就可以通過本題目了。

const int N = 5e5;
int n, m, num, cnt, q, siz[N], dep[N], dfn[N], id[N], ask[N], f[N];
int top[N], son[N], mark[N], tp;
vector<int> ver[N];
int cmp(int a, int b) { return dfn[a] < dfn[b]; }
void dfs1(int x, int fa) {
  dfn[x] = ++cnt;
  id[cnt] = x;
  siz[x] = 1;
  dep[x] = dep[fa] + 1;
  for (int i = 0; i < ver[x].size(); i++) {
    int to = ver[x][i];
    if (to == fa)
      continue;
    f[to] = x;
    dfs1(to, x);
    siz[x] += siz[to];
    if (siz[to] > siz[son[x]])
      son[x] = to;
  }
}
void dfs2(int now, int x) {
  top[now] = x;
  if (son[now])
    dfs2(son[now], x);
  for (int i = 0; i < ver[now].size(); i++) {
    int to = ver[now][i];
    if (to == f[now] || to == son[now])
      continue;
    dfs2(to, to);
  }
}
int LCA(int x, int y) {
  while (top[x] != top[y]) {
    if (dep[top[x]] >= dep[top[y]])
      x = f[top[x]];
    else
      y = f[top[y]];
  }
  return dep[x] < dep[y] ? x : y;
}
int dp[N], dp2[N];
vector<int> p[N];
int getans(int x) {
  int ret = 0;
  for (int i = 0; i < p[x].size(); i++)
    ret += getans(p[x][i]);
  if (mark[x]) {
    for (int i = 0; i < p[x].size(); i++)
      if (mark[p[x][i]])
        ret++;
  } else {
    int bel = 0;
    for (int i = 0; i < p[x].size(); i++)
      if (mark[p[x][i]])
        bel++;
    if (bel > 1)
      ret++;
    else if (bel == 1)
      mark[x] = 1;
  }
  return ret;
}
signed main() {
  n = read();
  for (int i = 1; i < n; i++) {
    int u = read(), v = read();
    ver[u].push_back(v);
    ver[v].push_back(u);
  }
  dfs1(1, 0);
  dfs2(1, 1);
  q = read();
  while (q--) {
    m = read();
    for (int i = 1; i <= m; i++)
      ask[i] = read(), mark[ask[i]] = 1;
    int fl = 0;
    for (int i = 1; i <= m; i++)
      if (mark[f[ask[i]]]) {
        fl = 1;
        break;
      }
    if (fl) {
      for (int i = 1; i <= m; i++)
        mark[ask[i]] = 0;
      write(-1);
      putc('\n');
      continue;
    }
    num = m;
    sort(ask + 1, ask + num + 1, cmp);
    for (int i = 2; i <= m; i++) {
      int lca = LCA(ask[i], ask[i - 1]);
      if (lca != ask[i] && lca != ask[i - 1])
        ask[++num] = lca;
    }
    sort(ask + 1, ask + num + 1);
    tp = num;
    num = unique(ask + 1, ask + num + 1) - (ask + 1);
    sort(ask + 1, ask + num + 1, cmp);
    for (int i = 2; i <= num; i++) {
      int lca = LCA(ask[i], ask[i - 1]);
      p[lca].push_back(ask[i]);
    }
    write(getans(ask[1]));
    putc('\n');
    for (int i = 1; i <= num; i++)
      mark[ask[i]] = 0;
    for (int i = 1; i <= num; i++)
      p[ask[i]].clear();
  }
  flush();
  return 0;
}

P4103 [HEOI2014]大工程

一顆邊權為單位邊權的 \(n\) 個節點的樹,執行 \(q\) 次操作,每次操作選擇 \(k\) 個點,在 \(k\) 個節點中兩兩建通道,通帶的代價為樹上兩點的距離。

求新通道中代價和,代價最小值,代價最大值。

\(n \leq 10^6\) , \(\sum k\leq 2\times 10^6\)


遇到這種題目現在就已經很明顯了吧...

先考慮朴素的轉移,進行樹上 \(dp\)

發現可行,但是中途發現是有很多無關節點的,仍然只與選中點,以及它們的 \(\text{LCA}\) 有關,那么建立出虛樹,然后進行虛樹 \(dp\) 就行了。

這個 \(dp\) 太簡單就不講了。

簡單來說一個思路就是考慮以父親為中轉站,然后兒子之間進行配對。

const int N = 2e6, INF = 2e9;
int n, q, m, num, cnt, dfn[N], siz[N], son[N], f[N], top[N], dep[N];
int ask[N], mark[N], ans1, ans2, ans3;
vector<int> ver[N], p[N];
void dfs1(int x, int fa) {
  dfn[x] = ++cnt;
  siz[x] = 1;
  dep[x] = dep[fa] + 1;
  for (int i = 0; i < ver[x].size(); i++) {
    int to = ver[x][i];
    if (to == fa)
      continue;
    f[to] = x;
    dfs1(to, x);
    siz[x] += siz[to];
    if (siz[to] > siz[son[x]])
      son[x] = to;
  }
}
void dfs2(int now, int x) {
  top[now] = x;
  if (son[now])
    dfs2(son[now], x);
  for (int i = 0; i < ver[now].size(); i++) {
    int to = ver[now][i];
    if (to == son[now] || to == f[now])
      continue;
    dfs2(to, to);
  }
}
int LCA(int x, int y) {
  while (top[x] != top[y]) {
    if (dep[top[x]] >= dep[top[y]])
      x = f[top[x]];
    else
      y = f[top[y]];
  }
  return dep[x] < dep[y] ? x : y;
}
int cmp(int x, int y) { return dfn[x] < dfn[y]; }
int mx[N], mn[N], s[N], g[N];
void dp(int x) {
  s[x] = mark[x];
  g[x] = 0;
  if (mark[x])
    mx[x] = mn[x] = 0;
  else
    mx[x] = -INF, mn[x] = INF;
  for (int i = 0; i < p[x].size(); i++) {
    int to = p[x][i];
    dp(to);
    int l = dep[to] - dep[x];
    ans1 += (g[x] + s[x] * l) * s[to] + g[to] * s[x];
    s[x] += s[to];
    g[x] += g[to] + l * s[to];
    ans2 = min(ans2, mn[x] + mn[to] + l);
    ans3 = max(ans3, mx[x] + mx[to] + l);
    mn[x] = min(mn[x], mn[to] + l);
    mx[x] = max(mx[x], mx[to] + l);
  }
}
signed main() {
  n = read();
  for (int i = 1; i < n; i++) {
    int a = read(), b = read();
    ver[a].push_back(b);
    ver[b].push_back(a);
  }
  dfs1(1, 0);
  dfs2(1, 1);
  q = read();
  while (q--) {
    m = read();
    for (int i = 1; i <= m; i++)
      ask[i] = read(), mark[ask[i]] = 1;
    sort(ask + 1, ask + m + 1, cmp);
    num = m;
    for (int i = 2; i <= m; i++) {
      int lca = LCA(ask[i], ask[i - 1]);
      if (lca != ask[i] && lca != ask[i - 1])
        ask[++num] = lca;
    }
    sort(ask + 1, ask + num + 1);
    num = unique(ask + 1, ask + num + 1) - (ask + 1);
    sort(ask + 1, ask + num + 1, cmp);
    for (int i = 2; i <= num; i++) {
      int lca = LCA(ask[i], ask[i - 1]);
      p[lca].push_back(ask[i]);
    }
    ans1 = 0;
    ans2 = INF;
    ans3 = -INF;
    dp(ask[1]);
    printf("%lld %lld %lld\n", ans1, ans2, ans3);
    for (int i = 1; i <= num; i++)
      p[ask[i]].clear(), mark[ask[i]] = 0;
  }
  return 0;
}

P3233 [HNOI2014]世界樹

一個邊權均為 \(1\)\(n\) 個節點的樹。

\(q\) 次操作,每次選定 \(m\) 個節點作為特殊節點,我們想找出每個特殊節點可以控制的節點數目,對於每個節點,他被離他最近的特殊節點支配。

\(n \leq 3\times 10^5\) , \(\sum m \leq 3\times 10^5\)


惡心的要死的題。

這道題依舊還是很明顯的暗示了你往虛樹上想。

然后這道題的難點不在於虛樹,在於想到虛樹后你怎么做這個東西。

首先我們考慮對於我們選中的這些節點他們的答案是由哪里來的。

兩個地方的貢獻,一個是虛樹上節點的貢獻,一個是虛樹外的節點的貢獻。

我們首先考慮虛樹上的節點的貢獻,這個東西其實很好算的。

首先我們考慮說對於一個議事處,首先他本身肯定是被他自己控制的。

然后這個就確定了,但是虛樹上就是會有非議事處節點,這個時候你就要去更新這個東西。

這個很明顯,他只會被在虛樹上的兒子和父親更新。

然后這個你可以通過兩個 \(\text{dfs}\) 解決。

我們思考一下對於虛樹外的節點怎么貢獻他們的答案。

先對這個虛樹外的節點分個類,有兩種可能情況。

節點是在虛樹上某個節點 \(u\) 的子樹內,那很明顯的是這些節點都是被 \(u\) 控制的。

那我們讓 \(u\) 的子樹大小減去他的子節點方向上的所有節點數就是這些節點的貢獻。

我們考慮怎么計算這個答案,等價於計算 \(u\) 在兒子 \(v\) 方向上的直接兒子。

這個很明顯可以轉化為減去 \(v\)\(dep_v-dep_u-1\) 級祖先的子樹大小。

這個東西很明顯可以長鏈,重剖,倍增亂搞。

然后考慮第二種情況,對於節點是在虛樹上節點 \(u\) 和兒子 \(v\) 之間的。

那么這個東西就是說你是有可能是控制兒子的那個點控制你,也有可能是控制父親的那個點控制你。

然后你就是要去判斷是到哪個節點的時候你這個東西是屬於父親那邊的,其他是屬於兒子那邊的。

那么很顯然的你只要找到一個分界點,滿足的是上面都屬於父親那邊,下面都屬於兒子那邊。

這個操作讓你想到了二分,然后他也是可以二分的。

還有一個細節需要注意。

我們要保證所有的節點都被算到貢獻,所以你的虛樹根節點一定要是從深度最低的根節點開始的。

而你的所給的虛樹節點中建出來的虛樹不一定是從根節點開始的。

比如只有一個節點的時候。

然后樣例就有這么一個情況還是比較良心的,但我還是調了一天

然后就這么做,然后思路就是這樣了,寫起來難寫的要死。

也可能是我太菜了,所以才覺得難寫。

/kel

const int N = 1e6;
int n, m, q, cnt, dfn[N], dep[N], top[N], siz[N], ask[N], son[N], f[N];
int num, tot, mark[N], dp[N], in[N], pl[N], ans[N], g[N];
vector<int> ver[N], p[N];
void dfs1(int x, int fa) {
  dfn[x] = ++cnt;
  dep[x] = dep[fa] + 1;
  siz[x] = 1;
  for (int i = 0; i < ver[x].size(); i++) {
    int to = ver[x][i];
    if (to == fa)
      continue;
    f[to] = x;
    dfs1(to, x);
    siz[x] += siz[to];
    if (siz[to] > siz[son[x]])
      son[x] = to;
  }
}
void dfs2(int now, int x) {
  top[now] = x;
  in[now] = ++tot;
  pl[tot] = now;
  if (son[now])
    dfs2(son[now], x);
  for (int i = 0; i < ver[now].size(); i++) {
    int to = ver[now][i];
    if (to == f[now] || to == son[now])
      continue;
    dfs2(to, to);
  }
}
int LCA(int x, int y) {
  while (top[x] != top[y]) {
    if (dep[top[x]] >= dep[top[y]]) // ok
      x = f[top[x]];                // ok
    else
      y = f[top[y]];
  }
  return dep[x] < dep[y] ? x : y; // ok
}
int jump(int x, int k) {
  int rt = 1;
  // cout << "cnmSb\n";
  while (k >= in[x] - in[top[x]] + 1 && x != rt) { // ok
    k -= (in[x] - in[top[x]] + 1);                 // ok
    x = f[top[x]];                                 // ok
  }                                                // ok
  return pl[in[x] - k];                            // ok
}
int getdis(int x, int y) { return dep[x] + dep[y] - (dep[LCA(x, y)] << 1); }
void get1(int x) {
  if (mark[x])
    dp[x] = x;
  else
    dp[x] = -1;
  for (int i = 0; i < p[x].size(); i++) {
    int to = p[x][i];
    get1(to);
    if (dp[to] != -1) {
      if (dp[x] == -1) {
        dp[x] = dp[to]; // ok
      } else {
        int d1 = getdis(x, dp[to]), d2 = getdis(x, dp[x]); // ok
        if (d1 < d2 || (d1 == d2 && dp[to] < dp[x]))
          dp[x] = dp[to]; // ok
      }
    }
  }
}
// ok
void get2(int x) {
  for (int i = 0; i < p[x].size(); i++) {
    int to = p[x][i];
    if (dp[to] == -1) {
      dp[to] = dp[x]; // ok
    } else {
      int d1 = getdis(to, dp[x]);  // ok
      int d2 = getdis(to, dp[to]); // ok
      if (d1 < d2 || (d1 == d2 && dp[x] < dp[to]))
        dp[to] = dp[x]; // ok
    }
    get2(to);
  }
}
// ok
void get3(int x) {
  ans[dp[x]] += siz[x];
  for (int i = 0; i < p[x].size(); i++) {
    int to = p[x][i];
    ans[dp[x]] -= siz[jump(to, dep[to] - dep[x] - 1)];
    get3(to);
  }
}
// ok
void get4(int x) {
  for (int i = 0; i < p[x].size(); i++) {
    int to = p[x][i];
    if (dp[x] != dp[to]) {
      int d1 = getdis(x, dp[x]);       // ok
      int d2 = getdis(to, dp[to]);     // ok
      int len = dep[to] - dep[x] - 1;  // ok
      int l = 0, r = len, p = len + 1; // ok
      while (l <= r) {
        int mid = (l + r) >> 1;
        if (d1 + mid < d2 + len + 1 - mid ||
            (d1 + mid == d2 + len + 1 - mid && dp[x] < dp[to]))
          p = mid, l = mid + 1; // ok
        else
          r = mid - 1; // ok
      }
      if (p == 0)
        ans[dp[to]] += siz[jump(to, len)] - siz[to]; // ok
      else if (p == len + 1)
        ans[dp[x]] += siz[jump(to, len)] - siz[to]; // ok
      else {
        ans[dp[x]] += siz[jump(to, len)] - siz[jump(to, len - p)]; // ok
        ans[dp[to]] += siz[jump(to, len - p)] - siz[to];           // ok
      }
    } else {
      ans[dp[x]] += siz[jump(to, dep[to] - dep[x] - 1)] - siz[to]; // ok
    }
    get4(to);
  }
}
// ok
int cmp(int x, int y) { return dfn[x] < dfn[y]; }
signed main() {
  n = read();
  for (int i = 1; i < n; i++) {
    int a = read(), b = read();
    ver[a].push_back(b);
    ver[b].push_back(a);
  }
  dfs1(1, 0);
  dfs2(1, 1);
  q = read();
  while (q--) {
    int fl = 0;
    m = read();
    for (int i = 1; i <= m; i++)
      ask[i] = read(), mark[ask[i]] = 1, g[i] = ask[i];
    sort(ask + 1, ask + m + 1, cmp);
    num = m;
    for (int i = 2; i <= m; i++) {
      int lca = LCA(ask[i], ask[i - 1]);
      if (ask[i] != lca && ask[i - 1] != lca)
        ask[++num] = lca;
    }
    sort(ask + 1, ask + num + 1);
    // cout << "cnmSb\n";
    num = unique(ask + 1, ask + num + 1) - (ask + 1); // ok
    sort(ask + 1, ask + num + 1, cmp);
    for (int i = 2; i <= num; i++) {
      int lca = LCA(ask[i], ask[i - 1]);
      p[lca].push_back(ask[i]);
    }
    if (ask[1] != 1)
      p[1].push_back(ask[1]);
    get1(1);
    get2(1);
    get3(1);
    get4(1);
    /*
    for(int i=1;i<=m;i++){
      printf("%lld ",dp[g[i]]);
    }
    printf("\n");
    */
    for (int i = 1; i <= m; i++) {
      printf("%lld ", ans[g[i]]);
    }
    printf("\n");
    for (int i = 1; i <= num; i++) {
      mark[ask[i]] = 0;
      p[ask[i]].clear();
      ans[ask[i]] = 0;
      dp[ask[i]] = 0;
    }
    p[1].clear();
    dp[1] = 0;
    ans[1] = 0;
    mark[1] = 0;
  }
  return 0;
}

P2495 [SDOI2011]消耗戰

給定一棵𝑛個點的樹(邊帶權)以及若干組關鍵點,對每一組求刪邊的最少代價(刪邊的代價為邊權)可以使關鍵點與1 號節點不連通。

\(𝑛≤2.5×10^5,∑𝑘 ≤5×10^5\)


這道題同樣的還是建立虛樹。

仍然考慮我們去進行 \(dp\) ,我們考慮的時候我們要將一個子樹的答案向上傳,所以就以子樹為狀態設立轉移。

設立 \(dp_u\) 表示在子樹 \(u\) 中的答案。

考慮當點 \(u\) 為關鍵點的時候,他是必須斷的,而他斷了那么其實下面的點就不用在斷了,所以這個時候 \(dp_u=mn_u\)

其中 \(mn_u\) 表示的是 \(u\)\(1\) 最小的邊權。

考慮不是關鍵點的時候,可斷可不斷,那么我們將這個與他子樹答案和進行比較取更小的答案就行了。

也就是 \(dp_u=\min(\sum dp_v,mn_u)\)

然后就可以解決這個問題了。

順便附贈一組數據方便調試

input:
10
1 2 6432
2 3 9694
3 4 9714
4 5 8890
4 6 9170
1 7 6361
1 8 1627
2 9 9824
7 10 9947
5
3 4 7 2
5 7 2 10 6 3
5 3 7 4 8 10
3 3 2 10
2 6 8
output:
12793
12793
14420
12793
8059

const int N = 1e6, INF = 2e16;
int n, q, m, num, cnt, son[N], top[N], siz[N], dfn[N], dep[N], f[N], mn[N];
int ask[N], mark[N], dp[N];
vector<int> ver[N], val[N], p[N];
void dfs1(int x, int fa) {
  siz[x] = 1;
  dfn[x] = ++cnt;
  dep[x] = dep[fa] + 1;
  for (int i = 0; i < ver[x].size(); i++) {
    int to = ver[x][i];
    int w = val[x][i];
    if (to == fa)
      continue;
    f[to] = x;
    mn[to] = min(mn[x], w);
    dfs1(to, x);
    siz[x] += siz[to];
    if (siz[to] > siz[son[x]])
      son[x] = to;
  }
}
void dfs2(int now, int x) {
  top[now] = x;
  if (son[now])
    dfs2(son[now], x);
  for (int i = 0; i < ver[now].size(); i++) {
    int to = ver[now][i];
    if (to == f[now] || to == son[now])
      continue;
    dfs2(to, to);
  }
}
int LCA(int x, int y) {
  while (top[x] != top[y]) {
    if (dep[top[x]] <= dep[top[y]])
      y = f[top[y]];
    else
      x = f[top[x]];
  }
  return dep[x] < dep[y] ? x : y;
}
int cmp(int x, int y) { return dfn[x] < dfn[y]; }
void getans(int x) {
  // cout << x << " " << mark[x] << " " << dp[x] << " " << mn[x] << "\n";
  int ret = 0;
  for (int i = 0; i < p[x].size(); i++) {
    int to = p[x][i];
    getans(to);
    //  if (x == 1)
    // cout << to << " " << dp[to] << "\n";
    ret = ret + dp[to];
  }
  if (mark[x]) {
    dp[x] = mn[x];
  } else {
    dp[x] = min(mn[x], ret);
  }
  // cout << x<<f[x] << " " << mark[x] << " " << dp[x] << " " << mn[x] << "\n";
}
signed main() {
  n = read();
  for (int i = 1; i <= n; i++)
    mn[i] = INF;
  for (int i = 1; i < n; i++) {
    int u = read(), v = read(), w = read();
    ver[u].push_back(v);
    val[u].push_back(w);
    ver[v].push_back(u);
    val[v].push_back(w);
  }
  dfs1(1, 0);
  dfs2(1, 1);
  q = read();
  while (q--) {
    m = read();
    for (int i = 1; i <= m; i++)
      ask[i] = read(), mark[ask[i]] = 1;
    sort(ask + 1, ask + m + 1, cmp);
    num = m;
    for (int i = 2; i <= m; i++) {
      int lca = LCA(ask[i], ask[i - 1]);
      if (lca != ask[i] && lca != ask[i - 1])
        ask[++num] = lca;
    }
    sort(ask + 1, ask + num + 1);
    num = unique(ask + 1, ask + num + 1) - (ask + 1);
    sort(ask + 1, ask + num + 1, cmp);
    for (int i = 2; i <= num; i++) {
      int lca = LCA(ask[i], ask[i - 1]);
      p[lca].push_back(ask[i]);
    }
    getans(ask[1]);
    printf("%lld\n", dp[ask[1]]);
    for (int i = 1; i <= num; i++)
      dp[ask[i]] = 0, mark[ask[i]] = 0, p[ask[i]].clear();
  }
  return 0;
} 

P4242 樹上的毒瘤

一顆 \(n\) 個節點的樹,每個點都有自己的權值 \(c\) 。進行 \(q\) 次操作,操作有兩種。

一種是改變兩個節點路徑之間節點的權值。

一種是查詢給定點集中,所有點到其他點的顏色段數和。

$n , q \leq 10^5 $ , \(\sum m \leq 2\times 10^5\)


又是超級難寫題。

個人認為這道題的思路其實是很簡單的。

首先你要會求顏色段數,這個很簡單對吧,就直接樹剖加上線段樹就完事了,也就是 這道題

然后會了這個我們就可以快速求出樹上任意兩點之間的顏色段數。

首先這題還是老套路的虛樹模式,直接先建出虛樹來。

考慮怎么做題,我們考慮是肯定要設立一個 \(dp\)

但是現在沒啥思路,我們就去看看建出來的虛樹。

這個樹你肯定要給他賦一個邊權但是邊權怎么賦值?

你考慮下圖。

我們希望做到的是點 \(1\) 到點 \(2\) 的邊權加上點 \(2\) 到點 \(3\) 的邊權之間是有某種關系的,最好是能相等的。

但是因為可能父親的顏色段與父親的父親產生沖突,這個是會有影響的。

那我們直接不管父親的顏色段,將邊權設為兩點顏色段數減去 \(1\)

最后需要用的時候我們在加上 \(1\) 就好了。

然后這個東西就很對了。

那么一條路徑的權值就是邊權和加上 \(1\)

然后你這個時候在統計路徑就變成了一個經典的換根 \(dp\)

然后你考慮你一個點向 \(m\) 個點找路徑那你就加上 \(m\) 就好了。

然后就用樹剖加上換根 \(dp\) 寫完了這道題。

const int N = 3e5;
int n, q, m, num, cnt, c[N], siz[N], dep[N], f[N], son[N], top[N];
int dfn[N], col[N], dfn2[N], tot, aske[N], mark[N], id[N];
vector<int> ver[N], p[N];
void dfs1(int x, int fa) {
  siz[x] = 1;
  dfn2[x] = ++tot;
  dep[x] = dep[fa] + 1;
  f[x] = fa;
  for (int i = 0; i < ver[x].size(); i++) {
    int to = ver[x][i];
    if (to == fa)
      continue;
    dfs1(to, x);
    siz[x] += siz[to];
    if (siz[to] > siz[son[x]])
      son[x] = to;
  }
}
void dfs2(int now, int x) {
  top[now] = x;
  dfn[now] = ++cnt;
  col[cnt] = c[now];
  if (son[now])
    dfs2(son[now], x);
  for (int i = 0; i < ver[now].size(); i++) {
    int to = ver[now][i];
    if (to == f[now] || to == son[now])
      continue;
    dfs2(to, to);
  }
}
int LCA(int x, int y) {
  while (top[x] != top[y]) {
    if (dep[top[x]] <= dep[top[y]])
      y = f[top[y]];
    else
      x = f[top[x]];
  }
  return dep[x] < dep[y] ? x : y;
}
int s[N], tag[N];
int ls(int p) { return p << 1; }
int rs(int p) { return p << 1 | 1; }
void push_up(int p, int mid) {
  s[p] = s[ls(p)] + s[rs(p)];
  if (col[mid] == col[mid + 1])
    s[p]--;
}
void pushdown(int p, int mid) {
  tag[ls(p)] = tag[rs(p)] = tag[p];
  col[mid] = col[mid + 1] = tag[p];
  s[ls(p)] = 1, s[rs(p)] = 1;
  tag[p] = 0;
}
void build(int p, int l, int r) {
  if (l == r) {
    s[p] = 1;
    return;
  }
  int mid = (l + r) >> 1;
  build(ls(p), l, mid), build(rs(p), mid + 1, r);
  push_up(p, mid);
}
void update(int p, int l, int r, int nx, int ny, int k) {
  if (nx <= l and r <= ny) {
    s[p] = 1;
    col[l] = col[r] = tag[p] = k;
    return;
  }
  int mid = (l + r) >> 1;
  if (tag[p])
    pushdown(p, mid);
  if (nx <= mid)
    update(ls(p), l, mid, nx, ny, k);
  if (ny > mid)
    update(rs(p), mid + 1, r, nx, ny, k);
  push_up(p, mid);
}
int ask(int p, int l, int r, int nx, int ny) {
  if (nx <= l && r <= ny)
    return s[p];
  int mid = (l + r) >> 1, ans = 0;
  if (tag[p])
    pushdown(p, mid);
  if (nx <= mid)
    ans = ask(ls(p), l, mid, nx, ny);
  if (ny > mid)
    ans += ask(rs(p), mid + 1, r, nx, ny);
  if (nx <= mid && ny > mid) {
    if (col[mid] == col[mid + 1])
      ans--;
  }
  return ans;
}
void addtree(int x, int y, int val) {
  while (top[x] != top[y]) {
    if (dep[top[x]] < dep[top[y]])
      swap(x, y);
    update(1, 1, n, dfn[top[x]], dfn[x], val);
    x = f[top[x]];
  }
  if (dep[x] > dep[y])
    swap(x, y);
  update(1, 1, n, dfn[x], dfn[y], val);
}
int asktree(int x, int y) {
  int ans = 0, u = x, v = y;
  while (top[x] != top[y]) {
    if (dep[top[x]] < dep[top[y]])
      swap(x, y);
    ans = (ans + ask(1, 1, n, dfn[top[x]], dfn[x]));
    x = f[top[x]];
  }
  if (dep[x] > dep[y])
    swap(x, y);
  ans = (ans + ask(1, 1, n, dfn[x], dfn[y]));
  while (top[u] != top[v]) {
    if (dep[top[u]] < dep[top[v]])
      swap(u, v);
    if (col[dfn[top[u]]] == col[dfn[f[top[u]]]])
      --ans;
    u = f[top[u]];
  }
  return ans;
}
int cmp(int x, int y) { return dfn2[x] < dfn2[y]; }
int dp[N], dp2[N], sz[N];
void get1(int x) {
  if (mark[x])
    sz[x] = 1;
  else
    sz[x] = 0;
  dp[x] = 0;
  for (int i = 0; i < p[x].size(); i++) {
    int to = p[x][i];
    int val = asktree(to, x) - 1;
    get1(to);
    sz[x] += sz[to];
    dp[x] += dp[to] + val * sz[to];
  }
}
void get2(int x) {
  for (int i = 0; i < p[x].size(); i++) {
    int to = p[x][i];
    int val = asktree(to, x) - 1;
    dp2[to] = dp2[x] + (m - 2 * sz[to]) * val;
    get2(to);
  }
}
signed main() {
  n = read(), q = read();
  for (int i = 1; i <= n; i++)
    c[i] = read();
  for (int i = 1; i < n; i++) {
    int a = read(), b = read();
    ver[a].push_back(b);
    ver[b].push_back(a);
  }
  dfs1(1, 0);
  dfs2(1, 1);
  build(1, 1, n);
  while (q--) {
    int op = read();
    if (op == 1) {
      int u = read(), v = read(), y = read();
      addtree(u, v, y);
    } else {
      m = read();
      for (int i = 1; i <= m; i++)
        aske[i] = read(), mark[aske[i]] = 1, id[i] = aske[i];
      sort(aske + 1, aske + m + 1, cmp);
      num = m;
      for (int i = 2; i <= m; i++) {
        int lca = LCA(aske[i], aske[i - 1]);
        if (lca != aske[i] && lca != aske[i - 1])
          aske[++num] = lca;
      }
      sort(aske + 1, aske + num + 1);
      num = unique(aske + 1, aske + num + 1) - (aske + 1);
      sort(aske + 1, aske + num + 1, cmp);
      for (int i = 2; i <= num; i++) {
        int lca = LCA(aske[i], aske[i - 1]);
        p[lca].push_back(aske[i]);
      }
      get1(aske[1]);
      dp2[aske[1]] = dp[aske[1]];
      get2(aske[1]);
      for (int i = 1; i <= m; i++)
        write(dp2[id[i]] + m), putc(' ');
      putc('\n');
      for (int i = 1; i <= num; i++)
        p[aske[i]].clear(), mark[aske[i]] = 0, dp2[aske[i]] = 0,
                            dp[aske[i]] = 0, sz[aske[i]] = 0, id[i] = 0;
    }
  }
  flush();
  return 0;
}

結語

其實如果看了這篇文章后可以發現的是,作者都只是寫了一些簡單的板子題,沒有思維的那種。

真正的難題在於想到虛樹,建虛樹。

作者在這篇文章前的言論是有錯誤的,虛樹的建立其實才是關鍵,不應該有偏駁,這是在寫完這些題去做升級題的時候了解到的。

希望大家看過這個垃圾文章能鞏固基礎( )

然后這一切都還是因為作者還太菜了。

有幸的話,作者將會把這篇文章升級,加入更多虛樹拓展。

但現在很抱歉,這篇文章鴿子了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM