極簡單數據結構亂寫

luogu P3979 遙遠的國度

鏈覆蓋,詢問子樹最小值, 換根。

lct 即可。

可以使用樹剖, 詢問的時候分類討論。

  1. id 和 root 相等, 直接詢問即可。
  2. root ≠ lca(id, root) ≠ id, 直接詢問即可
  3. id 是 root 的祖先, 相當於詢問 id 除 root 所在的子樹之外的位置, 在 dfs 序上分解即可
  4. root 是 id 的祖先, 直接詢問即可
#include<bits/stdc++.h>

using namespace std;

const int N = 1e5 + 3, inf = 2147483647;

int root;
int n, m, val[N], a[N];
int ecnt, hd[N], nt[N*2+1], vr[N*2+1];
void add(int x, int y) {nt[++ecnt]=hd[x],hd[x]=ecnt; vr[ecnt]=y;}

int dep[N], siz[N], fa[N], tp[N], son[N];
void dfs1(int x, int F, int D) {
  dep[x] = D, siz[x] = 1, fa[x] = F;
  for(int i = hd[x]; i; i = nt[i]) {
    int y = vr[i];
    if(y == F) continue;
    dfs1(y, x, D + 1);
    siz[x] += siz[y];
    if(siz[y] > siz[son[x]]) son[x] = y;
  }
}
int dfntot, in[N], out[N];
void dfs2(int x, int T) {
  in[x] = ++dfntot; a[in[x]] = val[x];
  tp[x] = T;
  if(son[x]) dfs2(son[x], T);
  for(int i = hd[x]; i; i = nt[i]) {
    int y = vr[i];
    if(y == fa[x] || y == son[x]) continue;
    dfs2(y, y);
  }
  out[x] = dfntot;
}

int up(int x, int D, int Top) {
  while(dep[tp[x]] > D) x = fa[tp[x]];
  return dep[tp[x]] == D ? tp[x] : son[Top];
}

int t[N<<2], tag[N<<2];
#define li (me << 1)
#define ri (li | 1)
#define mid ((l+r)>>1)
#define ls li,l,mid
#define rs ri,mid+1,r
void build(int me, int l, int r) {
  if(l == r) { t[me] = a[l]; return; }
  build(ls), build(rs), t[me] = min(t[li], t[ri]);
}
void ad(int me, int v) { t[me] = tag[me] = v;}
void pushdown(int me) {
  if(tag[me]) {ad(li, tag[me]), ad(ri, tag[me]), tag[me] = 0; }
}
void modi(int me, int l, int r, int x, int y, int v) {
  if(x <= l && r <= y) { ad(me, v); return;}
  pushdown(me);
  if(x<=mid) modi(ls, x, y, v);
  if(y >mid) modi(rs, x, y, v);
  t[me] = min(t[li], t[ri]);
}
int ask(int me, int l, int r, int x, int y) {
  if(x <= l && r <= y) return t[me];
  pushdown(me);
  int ret = inf;
  if(x<=mid) ret = ask(ls, x, y);
  if(y >mid) ret = min(ret, ask(rs, x, y));
  return ret;
}
void road_modi(int x, int y, int v) {
  while(tp[x] != tp[y]) {
    if(dep[tp[y]] > dep[tp[x]]) swap(x, y);
    modi(1, 1, n, in[tp[x]], in[x], v);
    x = fa[tp[x]];
  }
  if(dep[y] > dep[x]) swap(x, y);
  modi(1, 1, n, in[y], in[x], v);
}

int main()
{
  scanf("%d%d", &n, &m);
  for(int i = 1; i < n; ++i) {
    int x, y; scanf("%d%d", &x, &y); add(x, y), add(y, x);
  }
  for(int i = 1; i <= n; ++i) scanf("%d", &val[i]);
  scanf("%d", &root);
  dfs1(1, 0, 1), dfs2(1, 1);
  
  build(1, 1, n);
  
  int opt, id, x, y, v;
  while(m--)
  {
    scanf("%d", &opt);
    if(opt == 1)
    {
      scanf("%d", &id);
      root = id;
    }
    else
    if(opt == 2)
    {
      scanf("%d%d%d", &x, &y, &v);
      road_modi(x, y, v);
    }
    else
    if(opt == 3)
    {
      scanf("%d", &id);
      if(id == root) cout << t[1] << '\n';
      else
      {
        if(in[id] <= in[root] && out[root] <= out[id])
        {
          int tmp = up(root, dep[id] + 1, id);
          int l = in[tmp] - 1, r = out[tmp] + 1;
          int ret = inf;
          if(1 <= l) ret = ask(1, 1, n, 1, l);
          if(r <= n) ret = min(ret, ask(1, 1, n, r, n));
          cout << ret << '\n';
        }
        else
          cout << ask(1, 1, n, in[id], out[id]) << '\n';
      }
    }
  }
  return 0;
}

對於一顆 n 個點的樹, 給定 m 個關鍵點, 可以構造一棵點數爲 O(m) 的保持原樹祖先關係的樹, 稱爲虛樹。爲了方便,通常會把原樹的根默認爲關鍵點。

預處理出原樹的 dfs 序, 這樣,按這個 dfs 序訪問每組關鍵點, 相當於在虛樹上 dfs, dfs 的過程中連邊並添加輔助點即可。

Luogu P2495 [SDOI2011] 消耗戰

設 dp[u] 爲割掉 u 子樹內所有關鍵點的最小代價, 若 u 爲關鍵點, 則 dp[u] 爲 u 到根的最小邊權,反之, dp[u] = min(u 到根的最小邊權,Σv∈u.son dp[v])。

發現 dp 子樹內沒有關鍵點的點是沒有意義的, 進而, 對於子樹內關鍵點集合相同的若干點, 有很多都是無用的, 於是在虛樹上 DP, 答案不變。

// 這段代碼是優化過的, 不是原始的虛樹 DP。具體地, 建出的虛樹的關鍵點都不記錄其子樹。
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
int read() {
  int x = 0; char c = getchar();
  while(c < '0' || c > '9') c = getchar();
  while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
  return x;
}
const int N = 250003;
const LL inf = 1e18;

int n;
int ecnt, hd[N], nt[N*2+1], vr[N*2+1], w[N*2+1];
void ad(int u, int v, int w_) {nt[++ecnt] = hd[u], hd[u] = ecnt, vr[ecnt] = v, w[ecnt] = w_; }

LL mi[N];
int dep[N], siz[N], fa[N], son[N], tp[N];
void dfs1(int x, int F, int D) {
  dep[x] = D, siz[x] = 1, fa[x] = F;
  for(int i = hd[x]; i; i = nt[i]) {
    int y = vr[i];
    if(y == F) continue;
    mi[y] = min(mi[x], (LL)w[i]);
    dfs1(y, x, D + 1);
    siz[x] += siz[y];
    if(siz[y] > siz[son[x]]) son[x] = y;
  }
}
int dfntot, dfn[N];
void dfs2(int x, int T) {
  dfn[x] = ++dfntot;
  tp[x] = T;
  if(son[x]) dfs2(son[x], T);
  for(int i = hd[x]; i; i = nt[i]) {
    int y = vr[i];
    if(y == fa[x] || y == son[x]) continue;
    dfs2(y, y);
  }
}
int lca(int x, int y) {
  while(tp[x] != tp[y]) dep[tp[x]] > dep[tp[y]] ? x = fa[tp[x]] : y = fa[tp[y]];
  return dep[x] > dep[y] ? y : x;
}


int k, h[N];
bool cmp(int s1, int s2) { return dfn[s1] < dfn[s2]; }
vector<int> v[N];

int s[N], t;
void extnd(int x) {
  if(t == 1) { s[++t] = x; return; }
  int l = lca(s[t], x);
  if(l == s[t]) {  return; }
  while(t > 1 && dfn[s[t - 1]] >= dfn[l]) v[s[t - 1]].push_back(s[t]), --t;
  if(l != s[t]) v[l].push_back(s[t]), s[t] = l;
  s[++t] = x;
}

LL dp(int x) {
  if(v[x].size() == 0) return mi[x];
  LL tmp = 0ll;
  for(int i = 0; i < (int)v[x].size(); ++i) tmp += dp(v[x][i]);
  v[x].clear();
  return min(mi[x], tmp);
}

int main()
{
  n = read();
  for(int i = 1; i < n; ++i) {
    int x = read(), y = read(), z = read(); ad(x, y, z), ad(y, x, z);
  }
  mi[1] = inf;
  dfs1(1, 0, 1), dfs2(1, 1);
  
  int m = read();
  while(m--)
  {
    k = read();
    for(int i = 1; i <= k; ++i) h[i] = read();
    sort(h + 1, h + 1 + k, cmp);
    s[t = 1] = 1;
    for(int i = 1; i <= k; ++i) extnd(h[i]);
    while(t > 0) v[s[t - 1]].push_back(s[t]), --t;
    printf("%lld\n", dp(1));
  }
  return 0;
}

定理:對於任何高度爲 O(log n) 的樹, Σu siz[u] = O(n log n)。

點分治時, 用當前層的中心當作下一層所有中心的父親, 就可以得到一棵點分治重構樹。

可根據上面的定理可以證明點分治的複雜度。

梳理下澱粉質板子。

//澱粉質的主過程,即構建澱粉樹的過程
void calc(int sta) {
    //第一步得到當前澱粉樹子樹的 size
    get_mid(sta, 0);
    //而後找出重心
    sumsize = siz[sta];
    get_mid(sta, 0);
    vis[mid] = true;
    
    //從這開始計算分治中心
    
    //從這結束計算分治中心
    
    //分治
    for(int i = hd[x]; i; i = nt[i]) {
        int y = vr[i];
        if(vis[y]) continue;
        calc(y);
    }
}

Luogu P4178 Tree

可以直接點分治, 像 DSU on Tree 那樣用 dfn 會爽許多。

由於 k 較小, 統計的時候可以用數據結構搞, 而不必用雙指針+容斥。

我會將兩種寫法都寫一遍。

// 數據結構統計, 缺點是難以應付 k 較大的情況
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;

const int N = 4e4 + 233, K = 2e4 + 233;

int n, k;
int ecnt, hd[N], nt[N*2+1], vr[N*2+1], we[N*2+1];
void ad(int u, int v, int w) {nt[++ecnt] = hd[u], hd[u] = ecnt; vr[ecnt] = v, we[ecnt] = w; }

LL ans = 0ll;

bool vis[N];
int siz[N];
int sumsiz, mid;
void get_mid(int x, int F) { int mx = 0;
  siz[x] = 1;
  for(int i = hd[x]; i; i = nt[i]) {
    int y = vr[i];
    if(y == F || vis[y]) continue;
    get_mid(y, x);
    siz[x] += siz[y];
    mx = max(mx, siz[y]);
  }
  mx = max(mx, sumsiz - siz[x]);
  if(mx <= (sumsiz / 2)) mid = x;
}

int dis[N];
int dfntot, in[N], out[N], row[N];
void dfs(int x, int F) {
  in[x] = ++dfntot; row[dfntot] = x;
  for(int i = hd[x]; i; i = nt[i]) {
    int y = vr[i];
    if(y == F || vis[y]) continue;
    dis[y] = dis[x] + we[i];
    dfs(y, x);
  }
  out[x] = dfntot;
}

int t[K];
void ins(int x) {
  if(!x) return;
  for(; x <= k; x += (x & (-x))) ++t[x];
}
int ask(int x) {
  int res = 0;
  for(; x; x -= (x & (-x))) res += t[x];
  return res;
}

void fuck(int x) {
  dfntot = 0;
  dis[x] = 0;
  dfs(x, 0);
  memset(t, 0, sizeof t);
  
  for(int i = hd[x]; i; i = nt[i]) {
    int y = vr[i];
    if(vis[y]) continue;
    for(int j = in[y]; j <= out[y]; ++j) {
      int Y = row[j];
      if(dis[Y] <= k) ++ans;
      if(dis[Y] < k) ans += ask(k - dis[Y]);
    }
    for(int j = in[y]; j <= out[y]; ++j) {
      int Y = row[j];
      if(dis[Y] < k) ins(dis[Y]);
    }
  }
}

void calc(int sta) {
  get_mid(sta, 0);
  sumsiz = siz[sta];
  get_mid(sta, 0);
  vis[mid] = true;
  
  if(siz[mid] > 1) fuck(mid);
  
  for(int i = hd[mid]; i; i = nt[i]) {
    int y = vr[i];
    if(vis[y]) continue;
    calc(y);
  }
}

int main()
{
  scanf("%d", &n);
  for(int i = 1; i < n; ++i) {
    int u, v, w; scanf("%d%d%d", &u, &v, &w); ad(u, v, w), ad(v, u, w);
  }
  scanf("%d", &k);
  calc(1);
  cout << ans;
  return 0;
}
// 雙指針+容斥, 有複雜度優勢, 常數不足爲懼
#include <bits/stdc++.h>
typedef long long LL;
using namespace std;

const int N = 4e4 + 23;

int n, k;
int ecnt, hd[N], nt[N*2+1], vr[N*2+1], we[N*2+1];
void ad(int u, int v, int w) { nt[++ecnt] = hd[u], hd[u] = ecnt; vr[ecnt] = v, we[ecnt] = w; }

LL ans = 0ll;
bool vis[N];

int siz[N];
int sumsiz, mid;
void get_mid(int x, int F) {int mx = 0;
  siz[x] = 1;
  for(int i = hd[x]; i; i = nt[i]) {
    int y =  vr[i];
    if(y == F || vis[y]) continue;
    get_mid(y, x);
    siz[x] += siz[y];
    mx = max(mx, siz[y]);
  }
  mx = max(mx, sumsiz - siz[x]);
  if(mx <= (sumsiz / 2)) mid = x;
}

int dis[N];
int dfntot, in[N], out[N], row[N];
void dfs(int x, int F) {
  in[x] = ++dfntot; row[dfntot] = x;
  for(int i = hd[x]; i; i = nt[i]) {
    int y = vr[i];
    if(y == F || vis[y]) continue;
    dis[y] = dis[x] + we[i];
    dfs(y, x);
  }
  out[x] = dfntot;
}

int s[N], t;

LL gao() {
  LL res = 0ll;
  sort(s+1, s+1+t);
  int l = 1, r = t;
  while(l < r && l <= t) {
    while(r >= 1 && s[r] + s[l] > k) --r;
    if(l < r) res += (r - l);
    ++l;
  }
  return res;
}

void fuck(int x) {
  dfntot = 0;
  dis[x] = 0;
  dfs(x, 0);
  
  t = 0;
  for(int i = in[x]; i <= out[x]; ++i) s[++t] = dis[row[i]];
  ans += gao();
  for(int i = hd[x]; i; i = nt[i]) {
    int y = vr[i];
    if(vis[y]) continue;
    
    t = 0;
    for(int j = in[y]; j <= out[y]; ++j) {
      s[++t] = dis[row[j]];
    }
    ans -= gao();
  }
}

void calc(int sta) {
  get_mid(sta, 0);
  sumsiz = siz[sta];
  get_mid(sta, 0);
  vis[mid] = true;
  
  if(siz[mid] > 1) fuck(mid);
  
  for(int i = hd[mid]; i; i = nt[i]) {
    int y = vr[i];
    if(vis[y]) continue;
    calc(y);
  }
}

int main() {
  
  scanf("%d", &n);
  for(int i = 1; i < n; ++i) {
    int u, v, w; scanf("%d%d%d", &u, &v, &w); ad(u, v, w), ad(v, u, w);
  }
  scanf("%d", &k);
  calc(1);
  cout << ans;
  return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章