树链剖分

树链剖分定义树链剖分用于将树分割成若干条链的形式,以维护树上路径的信息。具体来说,将整棵树剖分为若干条链,使它组合成线性结构,然后用其他的数据结构维护信息。树链剖分(树剖/链剖)有多种形式,如重链剖分,长链剖分和用于Link/cutTree的剖分(有时被称作“实链剖分”),大多数情况下(没有

大家好,欢迎来到IT知识分享网。

定义

树链剖分用于将树分割成若干条链的形式,以维护树上路径的信息。

具体来说,将整棵树剖分为若干条链,使它组合成线性结构,然后用其他的数据结构维护信息。

树链剖分(树剖/链剖)有多种形式,如 重链剖分长链剖分 和用于 Link/cut Tree 的剖分(有时被称作“实链剖分”),大多数情况下(没有特别说明时),“树链剖分”都指“重链剖分”。

重链剖分可以将树上的任意一条路径划分成不超过\(O(logn)\)条连续的链,每条链上的点深度互不相同(即是自底向上的一条链,链上所有点的 LCA 为链的一个端点)。

重链剖分还能保证划分出的每条链上的节点 DFS 序连续,因此可以方便地用一些维护序列的数据结构(如线段树)来维护树上路径的信息。

如:

  1. 修改 树上两点之间的路径上 所有点的值。
  2. 查询 树上两点之间的路径上 节点权值的 和/极值/其它(在序列上可以用数据结构维护,便于合并的信息)

除了配合数据结构来维护树上路径信息,树剖还可以用来 \(O(logn)\)(且常数较小)地求 LCA。在某些题目中,还可以利用其性质来灵活地运用树剖。

来自OIWIKI

重链剖分

性质

  • 经过一条轻边,子树大小翻倍,所以只会经过\(O(logn)\)条轻边
  • 两条路径最后一定会跳到同一条重链上

如此一来,旧可以把树上路径问题转变成\(logn\)个区间问题,用上一些数据结构进行维护。

例题

[ZJOI2008]树的统计

题目描述

一棵树上有 \(n\) 个节点,编号分别为 \(1\)\(n\),每个节点都有一个权值 \(w\)

我们将以下面的形式来要求你对这棵树完成一些操作:

I. CHANGE u t : 把结点 \(u\) 的权值改为 \(t\)

II. QMAX u v: 询问从点 \(u\) 到点 \(v\) 的路径上的节点的最大权值。

III. QSUM u v: 询问从点 \(u\) 到点 \(v\) 的路径上的节点的权值和。

注意:从点 \(u\) 到点 \(v\) 的路径上的节点包括 \(u\)\(v\) 本身。

输入格式

输入文件的第一行为一个整数 \(n\),表示节点的个数。

接下来 \(n-1\) 行,每行 \(2\) 个整数 \(a\)\(b\),表示节点 \(a\) 和节点 \(b\) 之间有一条边相连。

接下来一行 \(n\) 个整数,第 \(i\) 个整数 \(w_i\) 表示节点 \(i\) 的权值。

接下来 \(1\) 行,为一个整数 \(q\),表示操作的总数。

接下来 \(q\) 行,每行一个操作,以 CHANGE u t 或者 QMAX u v 或者 QSUM u v 的形式给出。

输出格式

对于每个 QMAX 或者 QSUM 的操作,每行输出一个整数表示要求输出的结果。

样例输入 #1
4
1 2
2 3
4 1
4 2 1 3
12
QMAX 3 4
QMAX 3 3
QMAX 3 2
QMAX 2 3
QSUM 3 4
QSUM 2 1
CHANGE 1 5
QMAX 3 4
CHANGE 3 6
QMAX 3 4
QMAX 2 4
QSUM 3 4
样例输出 #1
4
1
2
2
10
6
5
6
5
16
提示

对于 \(100 \%\) 的数据,保证 \(1\le n \le 3\times 10^4\)\(0\le q\le 2\times 10^5\)

中途操作中保证每个节点的权值 \(w\)\(-3\times 10^4\)\(3\times 10^4\) 之间。

思路

树链剖分以后对整个DFS序建线段树,这样子重链的一段就是连续的一段,可以直接\(O(logn)\)的时间复杂度下解决区间求和和求最大值,轻链部分则一步步挪,由于最多会有\(logn\)的轻链,所以只会移动\(logn\)次。

每次查询的是跳过的点,不包括下次要到达的点。类似于左闭右开区间。

代码实现
const int N = 101000;

int n, m, a[N];
vector<int> e[N];
int l[N], r[N], idx[N];
int sz[N], hs[N], tot, top[N], dep[N], fa[N];

template<class T>void rd(T &x) {
  x=0; int f=0; char ch=getchar();
  while (ch<'0'||ch>'9')  {f|=(ch=='-');ch=getchar();}
  while (ch>='0'&&ch<='9') {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
  x=f?-x:x;
  return;
}

struct info {
	int maxv, sum;
};

info operator + (const info &l, const info &r) {
	return (info){max(l.maxv, r.maxv), l.sum + r.sum};
}

struct node {
	info val;
} seg[N * 4];

// [l, r]

void update(int id) {
	seg[id].val = seg[id * 2].val + seg[id * 2 + 1].val;
}

void build(int id, int l, int r) {
	if (l == r) {
		seg[id].val = {a[idx[l]], a[idx[l]]};
	} else {
		int mid = (l + r) / 2;
		build(id * 2, l, mid);
		build(id * 2 + 1, mid + 1, r);
		update(id);
	}
}

// 节点为id,对应的区间为[l, r],修改a[pos] -> val
void change(int id, int l, int r, int pos, int v) {
	if (l == r) {
    // l -> dfsn-> l
		seg[id].val = {v, v};
	} else {
		int mid = l + r >> 1;
		if (pos <= mid) change(id * 2, l, mid, pos, v);
		else change(id * 2 + 1, mid + 1, r, pos, v);
		// 重要‼️
		update(id);
	}
} 
// [ql, qr]表示查询的区间
info query(int id, int l, int r, int ql, int qr) {
	if (l == ql && r == qr) return seg[id].val;
	int mid = (l + r) / 2;
	// [l, mid] , [mid + 1, r]
	if (qr <= mid) return query(id * 2, l, mid, ql, qr);
	else if (ql > mid) return query(id * 2 + 1, mid + 1, r, ql,qr);
	else {
		// qr > mid, ql <= mid
		// [ql, mid], [mid + 1, qr]
		return query(id * 2, l, mid, ql, mid) + 
			query(id * 2 + 1, mid + 1, r, mid + 1, qr);
	}
}

info query(int u, int v) {
  info ans{(int)-1e9, 0};
  while (top[u] != top[v]) {
    if (dep[top[u]] < dep[top[v]]) {
      // 维护的是跳过的点的信息,不包括到达的点,所以是top[v]
      ans = ans + query(1, 1, n, l[top[v]], l[v]);
      v = fa[top[v]];
    } else {
      ans = ans + query(1, 1, n, l[top[u]], l[u]);
      u = fa[top[u]];
    }
  }
  return dep[u] < dep[v] ? ans = ans + query(1,1,n,l[u],l[v]) : ans = ans + query(1,1,n,l[v],l[u]);
}

void dfs_init(int u, int f) {
  sz[u] = 1;
  hs[u] = -1;
  dep[u] = dep[f] + 1;
  fa[u] = f;
  for (auto v : e[u]) {
    if (v == f) continue;
    dfs_init(v, u);
    sz[u] += sz[v];
    if (hs[u] == -1 || sz[v] > sz[hs[u]]) 
      hs[u] = v;
  }
}

// 求出重链链头元素,每个点DFS序
void dfs2(int u, int t) {
  l[u] = ++tot;
  top[u] = t;
  idx[tot] = u;
  // 先访问重儿子
  if (hs[u] != -1) dfs2(hs[u], t);
  for (auto v : e[u]) 
    if (v != fa[u] && v != hs[u]) 
      dfs2(v, v);
  r[u] = tot;
}

int main(void) {
  //ios::sync_with_stdio(false),cin.tie(nullptr),cout.tie(nullptr);
  rd(n);
  rep(i,1,n-1) {
    int u, v;
    rd(u), rd(v);
    e[u].pb(v), e[v].pb(u);
  }
  rep(i,1,n) rd(a[i]);
  dfs_init(1,0);
  dfs2(1,1);
  build(1,1,n);
  rd(m);
  rep(i,1,m) {
    static char op[20];
    scanf("%s",op);
    int u, v;
    rd(u), rd(v);
    if (op[0] == 'C') change(1, 1, n, l[u], v);
    else {
      info ans = query(u, v);
      if (op[1] == 'M') printf("%d\n",ans.maxv);
      else printf("%d\n",ans.sum);
    }
  }
  return 0;
}

SDOI2011,染色

题目描述

给定一棵 \(n\) 个节点的无根树,共有 \(m\) 个操作,操作分为两种:

  1. 将节点 \(a\) 到节点 \(b\) 的路径上的所有点(包括 \(a\)\(b\))都染成颜色 \(c\)
  2. 询问节点 \(a\) 到节点 \(b\) 的路径上的颜色段数量。

颜色段的定义是极长的连续相同颜色被认为是一段。例如 112221 由三段组成:112221

输入格式

输入的第一行是用空格隔开的两个整数,分别代表树的节点个数 \(n\) 和操作个数 \(m\)

第二行有 \(n\) 个用空格隔开的整数,第 \(i\) 个整数 \(w_i\) 代表结点 \(i\) 的初始颜色。

\(3\) 到第 \((n + 1)\) 行,每行两个用空格隔开的整数 \(u, v\),代表树上存在一条连结节点 \(u\) 和节点 \(v\) 的边。

\((n + 2)\) 到第 \((n + m + 1)\) 行,每行描述一个操作,其格式为:

每行首先有一个字符 \(op\),代表本次操作的类型。

  • \(op\)C,则代表本次操作是一次染色操作,在一个空格后有三个用空格隔开的整数 \(a, b, c\),代表将 \(a\)\(b\) 的路径上所有点都染成颜色 \(c\)
  • \(op\)Q,则代表本次操作是一次查询操作,在一个空格后有两个用空格隔开的整数 \(a, b\),表示查询 \(a\)\(b\) 路径上的颜色段数量。
样例输入
6 5
2 2 1 2 1 1
1 2
1 3
2 4
2 5
2 6
Q 3 5
C 2 1 1
Q 3 5
C 5 1 2
Q 3 5
样例输出
3
1
2
思路

树链剖分后,用线段树维护树的路径信息。

整棵树可以剖成若干成—-····—–·—-··——-这样的链状结构

剖分之后,按先重后轻的顺序求出整棵树的DFS序列,可以发现重链的DFS序编号是连续的。

对整个DFS序建线段树,对于每段重链都可以直接变换成DFS序的对应段。

如何找出路径颜色段数

考虑两个区间:左区间123和右区间321,左边段数是3,右边段数也是3,合并后新区间为123321,总段数是5。

可以看到,两端合并的时候,会造成影响的其实是左区间的右端点和右区间的左端点是否一致,如果一致,则答案数目减少一段,不一致则是左右区间段数直接相加。

如何进行段数合并

合并的时候需要分清楚当前跳的是哪一条链,因为路径是呈现人字形的,左边路径跳上来途中无法和右边路径进行归并,所以要分别记录左右两边链的信息。

同一条链进行信息更新的时候,也需要注意方向,因为我们是依照DFS序从上往下建立的线段树,左端点在上右端点在下,但是我们求路径的时候是从下往上求的,就好比左端点在下,右端点在上,所以要颠倒左右端点。

例如:

\[new :[l_2,r_2],last:[l_1,r_1],两端区间合并后应该为[l_2, r_1],新区间的左端点要成为总答案的左端点。\\ 如此一来,再来一个新区间需要合并的时候,last的左端点就会刚好冲着new的右端点。 \]

1. ansv = query(1, 1, n, l[top[v]], l[v]) + ansv;// √
2. ansv = ansv + query(1, 1, n, l[top[v]], l[v]);// ×

多dao’dao几句,此处记一下困扰多时的地方:方向问题,DFS从上到下,类比成数组形式就是1->2->3… 如此扩展下去,左端点是上边深度浅的点,右端点是下边深度大的点,但是我们从下往上做的时候,,左端点就是深度大的点,右端点就是深度浅的点,如此一来区间信息就会对不上号,所以需要交换一下端点值。

代码实现
const int N = 1e5 + 10;

int n, m, tot;
int l[N], r[N], idx[N], sz[N], hs[N], top[N], fa[N], w[N],dep[N];
vector<int> e[N];

struct info {
  int lc, rc, sum;
};

info operator + (const info &l, const info &r) {
  info res;
  res.lc = l.lc, res.rc = r.rc, res.sum = l.sum + r.sum;
  if (l.rc == r.lc) {
    --res.sum;
  }
  return res;
}

struct SegmentTree {
  #define lson id << 1
  #define rson id << 1 | 1

  struct Tree {
    info val;
    int lazy;
  } seg[N << 2];

  inline void update(int id) {
    seg[id].val = seg[lson].val + seg[rson].val;
  }

  inline void settag(int id, int c) {
    seg[id].val = {c, c, 1};
    seg[id].lazy = c;
  }

  inline void pushdown(int id) {
    if (seg[id].lazy != 0) {
      settag(lson, seg[id].lazy);
      settag(rson, seg[id].lazy);
      seg[id].lazy = 0;
    }
  }

  inline void bulid(int id, int l, int r) {
    seg[id].lazy = 0;
    if (l == r) {
      seg[id].val = {w[idx[l]], w[idx[l]], 1};
    } else {
      int mid = l + r >> 1;
      bulid(lson, l, mid);
      bulid(rson, mid + 1, r);
      update(id);
    }
  }
  
  inline void modify(int id, int l, int r, int ql, int qr, int c) {
    if (l == ql && r == qr) {
      settag(id, c);
    } else {
      pushdown(id);
      int mid = l + r >> 1;
      if (qr <= mid) {
        modify(lson, l, mid, ql, qr, c);
      } else if (ql > mid) {
        modify(rson, mid + 1, r, ql, qr, c);
      } else {
        modify(lson, l, mid, ql, mid,c);
        modify(rson, mid + 1, r, mid + 1, qr, c);
      }
      update(id);
    }
  }

  inline info query(int id, int l, int r, int ql, int qr) {
    if (l == ql && r == qr) {
      return seg[id].val;
    } else {
      pushdown(id);
      int mid = l + r >> 1;
      if (qr <= mid) {
        return query(lson, l, mid, ql, qr);
      } else if (ql > mid) {
        return query(rson,  mid + 1, r, ql, qr);
      } else {
        return query(lson, l, mid, ql, mid) + 
                query(rson, mid + 1, r, mid + 1, qr);
      }
    }
  }
} tree;

inline int read() {
  int f = 1, x = 0;char ch = getchar();
  while (ch > '9' || ch < '0'){if (ch == '-')f = -f;ch = getchar();}
  while (ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();}
  return x * f;
}

inline void dfs1(int u, int f) {
  sz[u] = 1;
  fa[u] = f;
  dep[u] = dep[f] + 1;
  hs[u] = -1;
  for (auto v : e[u]) {
    if (v == f) continue;
    dfs1(v, u);
    sz[u] += sz[v];
    if (hs[u] == -1 || sz[v] > sz[hs[u]]) {
      hs[u] = v;
    }
  }
}

inline void dfs2(int u, int t) {
  l[u] = ++tot;
  top[u] = t;
  idx[tot] = u;
  if (hs[u] != -1) dfs2(hs[u], t);
  for (auto v : e[u]) {
    if (v != fa[u] && v != hs[u]) {
      dfs2(v, v);
    }
  }
  r[u] = tot;
}

inline info query(int u, int v) {
  info ansl = {0,0,0}, ansr = {0,0,0};
  while (top[u] != top[v]) {
    if (dep[top[u]] > dep[top[v]]) {
      ansl = tree.query(1, 1, n, l[top[u]], l[u]) + ansl;
      u = fa[top[u]];
    } else {
      ansr = tree.query(1, 1, n, l[top[v]], l[v]) + ansr;
      v = fa[top[v]];
    }
  }
  if (dep[u] > dep[v]) {
    ansl = tree.query(1, 1, n, l[v], l[u]) + ansl;
  } else {
    ansr = tree.query(1, 1, n, l[u], l[v]) + ansr;
  }
  swap(ansl.lc, ansl.rc);
  return ansl + ansr;
}

inline void modify(int u, int v, int c) {
  while (top[u] != top[v]) {
    if (dep[top[u]] < dep[top[v]]) {
      swap(u, v);
    }
    tree.modify(1, 1, n, l[top[u]], l[u], c);
    u = fa[top[u]];
  }  
  if (dep[u] < dep[v]) swap(u, v);
  tree.modify(1, 1, n, l[v], l[u], c);
}

int main(void) {
  n = read(), m = read();
  rep(i,1,n) w[i] = read();
  rep(i,1,n-1) {
    int u, v;
    u = read(), v = read();
    e[u].push_back(v);
    e[v].push_back(u);
  }
  dfs1(1,0);
  dfs2(1,1);
  tree.bulid(1, 1, n);
  rep(i,1,m) {
    static char op[20];
    scanf("%s",op);
    if (op[0] == 'Q') {
      int u, v;
      u = read(), v = read();
      printf("%d\n",query(u,v).sum);
    } else {
      int u, v, c;
      u = read(), v = read(), c = read();
      modify(u, v, c);
    }
  }
  return 0;
}

NOI2015, 软件包管理器

题目背景

Linux 用户和 OSX 用户一定对软件包管理器不会陌生。通过软件包管理器,你可以通过一行命令安装某一个软件包,然后软件包管理器会帮助你从软件源下载软件包,同时自动解决所有的依赖(即下载安装这个软件包的安装所依赖的其它软件包),完成所有的配置。Debian/Ubuntu 使用的 apt-get,Fedora/CentOS 使用的 yum,以及 OSX 下可用的 homebrew 都是优秀的软件包管理器。

题目描述

你决定设计你自己的软件包管理器。不可避免地,你要解决软件包之间的依赖问题。如果软件包 \(a\) 依赖软件包 \(b\),那么安装软件包 \(a\) 以前,必须先安装软件包 \(b\)。同时,如果想要卸载软件包 \(b\),则必须卸载软件包 \(a\)

现在你已经获得了所有的软件包之间的依赖关系。而且,由于你之前的工作,除 \(0\) 号软件包以外,在你的管理器当中的软件包都会依赖一个且仅一个软件包,而 \(0\) 号软件包不依赖任何一个软件包。且依赖关系不存在环(即不会存在 \(m\) 个软件包 \(a_1,a_2, \dots , a_m\),对于 \(i<m\)\(a_i\) 依赖 \(a_{i+1}\),而 \(a_m\) 依赖 \(a_1\) 的情况)。

现在你要为你的软件包管理器写一个依赖解决程序。根据反馈,用户希望在安装和卸载某个软件包时,快速地知道这个操作实际上会改变多少个软件包的安装状态(即安装操作会安装多少个未安装的软件包,或卸载操作会卸载多少个已安装的软件包),你的任务就是实现这个部分。

注意,安装一个已安装的软件包,或卸载一个未安装的软件包,都不会改变任何软件包的安装状态,即在此情况下,改变安装状态的软件包数为 \(0\)

输入格式

第一行一个正整数 \(n\),表示软件包个数,从 \(0\) 开始编号。
第二行有 \(n-1\) 个整数,第 \(i\) 个表示 \(i\) 号软件包依赖的软件包编号。
然后一行一个正整数 \(q\),表示操作个数,格式如下:

  • install x 表示安装 \(x\) 号软件包
  • uninstall x 表示卸载 \(x\) 号软件包

一开始所有软件包都是未安装的。

对于每个操作,你需要输出这步操作会改变多少个软件包的安装状态,随后应用这个操作(即改变你维护的安装状态)。

输出格式

输出 \(q\) 行,每行一个整数,表示每次询问的答案。

样例输入 #1
7
0 0 0 1 1 5
5
install 5
install 6
uninstall 1
install 4
uninstall 0
样例输出 #1
3
1
3
2
3
样例输入 #2
10
0 1 2 1 3 0 0 3 2
10
install 0
install 3
uninstall 2
install 7
install 5
install 9
uninstall 9
install 4
install 1
install 9
样例输出 #2
1
3
2
1
3
1
1
1
0
1
思路

树链剖分后,用线段树维护区间1的信息,每次查询其实就是上一次查询-本次查线段树根节点的信息的绝对值。

单条链网上不断修改,比较的好写。

代码实现
const int N = 1e5 + 10;

int n, m, tot;
int l[N], r[N], idx[N], sz[N], hs[N], top[N], fa[N], w[N],dep[N];
vector<int> e[N];

struct SegmentTree {
  #define lson id << 1
  #define rson id << 1 | 1

  struct Tree {
    int cnt, sz, lazy;
  } seg[N << 2];

  inline void update(int id) {
    seg[id].cnt = seg[lson].cnt + seg[rson].cnt;
  }

  inline void settag(int id, int c) {
    if (c == 1) {
      seg[id].cnt = seg[id].sz;
    } else if (c == 0) {
      seg[id].cnt = 0;
    }
    seg[id].lazy = c;
  }

  inline void pushdown(int id) {
    if (seg[id].lazy != -1) {
      settag(lson, seg[id].lazy);
      settag(rson, seg[id].lazy);
      seg[id].lazy = -1;
    }
  }

  inline void bulid(int id, int l, int r) {
    seg[id].sz = r - l + 1;
    seg[id].lazy = -1;
    if (l == r) {
      return;
    } else {
      int mid = l + r >> 1;
      bulid(lson, l, mid);
      bulid(rson, mid + 1, r);
      update(id);
    }
  }
  
  inline void modify(int id, int l, int r, int ql, int qr, int c) {
    if (l == ql && r == qr) {
      settag(id, c);
    } else {
      pushdown(id);
      int mid = l + r >> 1;
      if (qr <= mid) {
        modify(lson, l, mid, ql, qr, c);
      } else if (ql > mid) {
        modify(rson, mid + 1, r, ql, qr, c);
      } else {
        modify(lson, l, mid, ql, mid,c);
        modify(rson, mid + 1, r, mid + 1, qr, c);
      }
      update(id);
    }
  }

} tree;

inline int read() {
  int f = 1, x = 0;char ch = getchar();
  while (ch > '9' || ch < '0'){if (ch == '-')f = -f;ch = getchar();}
  while (ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();}
  return x * f;
}

inline void dfs1(int u, int f) {
  sz[u] = 1;
  fa[u] = f;
  dep[u] = dep[f] + 1;
  hs[u] = -1;
  for (auto v : e[u]) {
    if (v == f) continue;
    dfs1(v, u);
    sz[u] += sz[v];
    if (hs[u] == -1 || sz[v] > sz[hs[u]]) {
      hs[u] = v;
    }
  }
}

inline void dfs2(int u, int t) {
  l[u] = ++tot;
  top[u] = t;
  idx[tot] = u;
  if (hs[u] != -1) dfs2(hs[u], t);
  for (auto v : e[u]) {
    if (v != fa[u] && v != hs[u]) {
      dfs2(v, v);
    }
  }
  r[u] = tot;
}

inline void install(int x) {
  while (x != 0) {
    tree.modify(1, 1, n, l[top[x]], l[x], 1);
    x = fa[top[x]];
  }
}

inline void uninstall(int x) {
  tree.modify(1, 1, n, l[x], r[x], 0);
}

int main(void) {
  n = read();
  rep(i,2,n) {
    fa[i] = read();
    ++fa[i];
    e[fa[i]].push_back(i);
  }
  dfs1(1, 0);
  dfs2(1, 1);
  tree.bulid(1,1,n);
  m = read();
  int pre = 0;
  rep(i,1,m) {
    int x;
    static char op[10];
    scanf("%s%d",op, &x);
    x++;
    if (op[0] == 'i') {
      install(x);
    } else {
      uninstall(x);
    }
    printf("%d\n",abs(pre - tree.seg[1].cnt));
    pre = tree.seg[1].cnt;
  }
  return 0;
}

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/30294.html

(0)

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

关注微信