树链剖分
“在一棵树上进行路径的修改、求极值、求和”乍一看只要线段树就能轻松解决,实际上,仅凭线段树是不能搞定它的。我们需要用到一种貌似高级的复杂算法——树链剖分。
树链,就是树上的路径。剖分,就是把路径分类为重链和轻链。
记siz[v]表示以v为根的子树的节点数,dep[v]表示v的深度(根深度为1),top[v]表示v所在的链的顶端节点,fa[v]表示v的父亲,son[v]表示与v在同一重链上的v的儿子节点(姑且称为重儿子),w[v]表示v与其父亲节点的连边(姑且称为v的父边)在线段树中的位置。只要把这些东西求出来,就能用logn的时间完成原问题中的操作。
重儿子:siz[u]为v的子节点中siz值最大的,那么u就是v的重儿子。
轻儿子:v的其它子节点。
重边:点v与其重儿子的连边。
轻边:点v与其轻儿子的连边。
重链:由重边连成的路径。
轻链:轻边。
剖分后的树有如下性质:
性质1:如果(v,u)为轻边,则siz[u] 2 < siz[v];
*性质2:从根到某一点的路径上轻链、重链的个数都不大于logn。
算法实现:
我们可以用两个dfs来求出fa、dep、siz、son、top、w。
dfs_1:把fa、dep、siz、son求出来,比较简单,略过。
dfs_2:⒈对于v,当son[v]存在(即v不是叶子节点)时,显然有top[son[v]] = top[v]。线段树中,v的重边应当在v的父边的后面,记w[son[v]] = totw+1,totw表示最后加入的一条边在线段树中的位置。此时,为了使一条重链各边在线段树中连续分布,应当进行dfs_2(son[v]);
⒉对于v的各个轻儿子u,显然有top[u]=u,并且w[u]=totw+1,进行dfs_2过程。这就求出了top和w。将树中各边的权值在线段树中更新,建链和建线段树的过程就完成了。
修改操作:例如将u到v的路径上每条边的权值都加上某值x。
一般人需要先求LCA,然后慢慢修改u、v到公共祖先的边。
记f1 = top[u],f2 = top[v]。
当f1 <> f2时:不妨设dep[f1] >= dep[f2],那么就更新u到f1的父边的权值(logn),并使u = fa[f1]。
当f1 = f2时:u与v在同一条重链上,若u与v不是同一点,就更新u到v路径上的边的权值(logn),否则修改完成;
重复上述过程,直到修改完成。
求和、求极值操作:类似修改操作,但是不更新边权,而是对其求和、求极值。
如图所示,较粗的为重边,较细的为轻边。节点编号旁边有个红色点的表明该节点是其所在链的顶端节点。边旁的蓝色数字表示该边在线段树中的位置。图中1-4-9-13-14为一条重链。
当要修改11到10的路径时。
第一次迭代:u = 11,v = 10,f1 = 2,f2 = 10。此时dep[f1] < dep[f2],因此修改线段树中的5号点,v = 4, f2 = 1;
第二次迭代:dep[f1] > dep[f2],修改线段树中10--11号点。u = 2,f1 = 2;
第三次迭代:dep[f1] > dep[f2],修改线段树中9号点。u = 1,f1 = 1;
第四次迭代:f1 = f2且u = v,修改结束。
题意:给一棵树,并给定各个点权的值,然后有3种操作:
I C1 C2 K: 把C1与C2的路径上的所有点权值加上K
D C1 C2 K:把C1与C2的路径上的所有点权值减去K
Q C:查询节点编号为C的权值
分析:典型的树链剖分题目,先进行剖分,然后用线段树去维护即可
#include<cstdio>
#include<cstring>
#include<stack>
#include<algorithm>
using namespace std;
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
typedef int lld;
stack<int> ss;
const int maxn = 200010;
const int inf = ~0u>>2;
int M[maxn<<2];
int add[maxn<<2];
struct node{
int s,t,w,next;
}edge[maxn*2];
int E,n;
int size[maxn] , fa[maxn] , heavy[maxn] , head[maxn] , vis[maxn];
int dep[maxn] , rev[maxn] , num[maxn] , cost[maxn] ,w[maxn];
int Seg_size;
inline void Max(int &x,int y){if(x<y) x=y;}
int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
void add_edge(int s,int t,int w)
{
edge[E].w=w;
edge[E].s=s;
edge[E].t=t;
edge[E].next=head[s];
head[s]=E++;
}
void dfs(int u,int f)
{
int mx=-1,e=-1;
size[u]=1;
for(int i=head[u];i!=-1;i=edge[i].next)
{
int v=edge[i].t;
if(v==f) continue;
edge[i].w=edge[i^1].w=w[v];
dep[v]=dep[u]+1;
rev[v]=i^1;
dfs(v,u);
size[u]+=size[v];
if(size[v]>mx)
{
mx=size[v];
e=i;
}
}
heavy[u]=e;
if(e!=-1) fa[edge[e].t]=u;
}
inline void pushup(int rt){
M[rt]=M[rt<<1]+M[rt<<1|1];
}
void pushdown(int rt,int m){
if(add[rt]){
add[rt<<1]+=add[rt];
add[rt<<1|1]+=add[rt];
M[rt<<1]+=add[rt]*(m-(m>>1));
M[rt<<1|1]+=add[rt]*(m>>1);
add[rt]=0;
}
}
void build(int l,int r,int rt){
M[rt]=add[rt]=0;
if(l==r){
return ;
}
int m=(l+r)>>1;
build(lson);
build(rson);
}
void update(int L,int R,int val,int l,int r,int rt){
if(L<=l&&r<=R){
M[rt]+=val;
add[rt]+=val;
return ;
}
pushdown(rt,r-l+1);
int m=(l+r)>>1;
if(L<=m) update(L,R,val,lson);
if(R>m) update(L,R,val,rson);
pushup(rt);
}
lld query(int L,int R,int l,int r,int rt){
if(L<=l&&r<=R){
return M[rt];
}
pushdown(rt,r-l+1);
int m=(l+r)>>1;
lld ret=0;
if(L<=m) ret+=query(L,R,lson);
if(R>m) ret+=query(L,R,rson);
return ret;
}
void prepare()
{
int i;
build(1,n,1);
memset(num,-1,sizeof(num));
dep[0]=0;Seg_size=0;
for(i=0;i<n;i++) fa[i]=i;
dfs(0,0);
for(i=0;i<n;i++)
{
if(heavy[i]==-1)
{
int pos=i;
while(pos && edge[heavy[edge[rev[pos]].t]].t == pos)
{
int t=rev[pos];
num[t]=num[t^1]=++Seg_size;//printf("pos=%d val=%d t=%d\n",Seg_size,edge[t].w,t);
update(Seg_size,Seg_size,edge[t].w,1,n,1);
pos=edge[t].t;
}
}
}
}
int lca(int u,int v)
{
while(1)
{
int a=find(u),b=find(v);
if(a==b) return dep[u] < dep[v] ? u : v;//a,b在同一条重链上
else if(dep[a]>=dep[b]) u=edge[rev[a]].t;
else v=edge[rev[b]].t;
}
}
void CH(int u,int lca,int val)
{
while(u!=lca)
{
int r=rev[u];//printf("r=%d\n",r);
if(num[r]==-1) edge[r].w+=val,u=edge[r].t;
else
{
int p=fa[u];
if(dep[p] < dep[lca]) p=lca;
int l=num[r];
r=num[heavy[p]];
update(l,r,val,1,n,1);
u=p;
}
}
}
void change(int u,int v,int val)
{
int p=lca(u,v);// printf("p=%d\n",p);
CH(u,p,val);
CH(v,p,val);
if(p){
int r=rev[p];
if(num[r]==-1) {
edge[r^1].w+=val;//在此处发现了我代码的重大bug
edge[r].w+=val;
}
else update(num[r],num[r],val,1,n,1);
}//根节点,特判
else w[p]+=val;
}
lld solve(int u)
{
if(!u) return w[u];//根节点,特判
else
{
int r=rev[u];
if(num[r]==-1) return edge[r].w;
else return query(num[r],num[r],1,n,1);
}
}
int main()
{
int t,i,a,b,c,m,ca=1,p;
while(scanf("%d%d%d",&n,&m,&p)!=EOF)
{
memset(head,-1,sizeof(head));
E=0;
for(i=0;i<n;i++) scanf("%d",&w[i]);
for(i=0;i<m;i++)
{
scanf("%d%d",&a,&b);a--;b--;
add_edge(a,b,0);
add_edge(b,a,0);
}
prepare();
char op[10];
while(p--)
{
scanf("%s",&op);
if(op[0]=='I')
{
scanf("%d%d%d",&a,&b,&c);a--;b--;
change(a,b,c);
}
else if(op[0]=='D')
{
scanf("%d%d%d",&a,&b,&c);a--;b--;
change(a,b,-c);
}
else
{
scanf("%d",&a);a--;
printf("%d\n",solve(a));
}
}
}
return 0;
}
Link-Cut Tree
动态树(Link-Cut Tree)是一类要求维护森林的连通性的题的总称,这类问题要求维护某个点到根的某些数据,支持树的切分,合并,以及对子树的某些操作。其中解决这一问题的某些简化版(不包括对子树的操作)的基础数据结构就是LCT(link-cut tree)。
LCT的大体思想类似于树链剖分中的轻重链剖分,轻重链剖分是处理出重链来,由于重链的定义和树链剖分是处理静态树所限,重链不会变化,变化的只是重链上的边或点的权值,由于这个性质,我们用线段树来维护树链剖分中的重链,但是LCT解决的是动态树问题(包含静态树),所以需要用更灵活的splay来维护这里的“重链”。
定义:
首先来定义一些量:
access(X):表示访问X点(之后会有说明)。
Preferred child(偏爱子节点):如果最后被访问的点在X的儿子P节点的子树中,那么称P为X的Preferred child,如果一个点被访问,他的Preferred child为null(即没有)。
Preferred edge(偏爱边):每个点到自己的Preferred child的边被称为Preferred edge。
Preferred path(偏爱路径):由Preferred edge组成的不可延伸的路径称为Preferred path。
这样我们可以发现一些比较显然的性质,每个点在且仅在一条Preferred path上,也就是所有的Preferred path包含了这棵树上的所有的点,这样一颗树就可以由一些Preferred path来表示(类似于轻重链剖分中的重链),我们用splay来维护每个条Preferred path,关键字为深度,也就是每棵splay中的点左子树的深度都比当前点小,右节点的深度都比当前节点的深度大。这样的每棵splay我们称为Auxiliary tree(辅助树),每个Auxiliary tree的根节点保存这个Auxiliary tree与上一棵Auxiliary tree中的哪个点相连。这个点称作他的Path parent。
操作:
access(X):首先由于preferred path的定义,如果一个点被访问,那么这个点到根节点的所有的边都会变成preferred edge,由于每个点只有一个preferred child,所以这个点到根节点路径上的所有的点都会和原来的preferred child断开,连接到这条新的preferred path上。假设访问X点,那么先将X点旋转到对应Auxiliary tree的根节点,然后因为被访问的点是没有preferred child的,所以将Auxiliary tree中根节点(X)与右子树的边断掉,左节点保留,将这个树的path parent旋转到对应Auxiliary tree的根节点,断掉右子树,连接这个点与X点,相当于合并两棵Auxiliary tree,不断地重复这一操作,直到当前X所在Auxiliary tree的path parent为null时停止,表示已经完成当前操作。
find root(x):找到某一点所在树的根节点(维护森林时使用)。只需要access(X),然后将X节点旋到对应Auxiliary tree的根节点,然后找到这个Auxiliary tree中最左面的点。
cut(x):断掉X节点和其父节点相连的边。首先access(X),然后将X旋转到对应Auxiliary tree的根节点,然后断掉Auxiliary tree中X和左节点相连的边。
link(join)(x,y):连接点x到y点上。即让x称为y的子节点。因为x为y的子节点后,在原x的子树中,x点到根节点的所有的点的深度会被翻转过来,所以先access(x),然后在对应的Auxiliary tree中将x旋转到根节点,,然后将左子树翻转(splay中的reverse操作),然后access(y),将y旋转到对应Auxiliary tree中的根节点,将x连到y就行了。
LCT的模板题,只需Link和Cut操作
#include<cstdio>
#include<cstring>
#include<iostream>
#define MAXN 50010
using namespace std;
int n;
struct LCT {
int bef[MAXN], pre[MAXN], next[MAXN][2];
void Init() {
memset(pre, 0, sizeof(pre));
memset(next, 0, sizeof(next));
}
inline void Rotate(int x, int kind) {
int y, z;
y = pre[x];
z = pre[y];
next[y][!kind] = next[x][kind];
pre[next[x][kind]] = y;
next[z][next[z][1] == y] = x;
pre[x] = z;
next[x][kind] = y;
pre[y] = x;
}
void Splay(int x) {
int rt;
for (rt = x; pre[rt]; rt = pre[rt])
;
if (x != rt) {
bef[x] = bef[rt];
bef[rt] = 0;
while (pre[x]) {
if (next[pre[x]][0] == x)
Rotate(x, 1);
else
Rotate(x, 0);
}
}
}
void Access(int x) {
int father;
for (father = 0; x; x = bef[x]) {
Splay(x);
pre[next[x][1]] = 0;
bef[next[x][1]] = x;
next[x][1] = father;
pre[father] = x;
bef[father] = 0;
father = x;
}
}
int Query(int x) {
Access(x);
Splay(x);
while (next[x][0])
x = next[x][0];
return x;
}
void Cut(int x) {
Access(x);
Splay(x);
bef[next[x][0]] = bef[x];
bef[x] = 0;
pre[next[x][0]] = 0;
next[x][0] = 0;
}
void Join(int x, int y) {
if (y == 0)
Cut(x);
else {
int tmp;
Access(y);
Splay(y);
for (tmp = x; pre[tmp]; tmp = pre[tmp])
;
if (tmp != y) {
Cut(x);
bef[x] = y;
}
}
}
} lct;
int INT() {
char ch;
int res;
while (ch = getchar(), !isdigit(ch))
;
for (res = ch - '0'; ch = getchar(), isdigit(ch);)
res = res * 10 + ch - '0';
return res;
}
char CHAR() {
char ch, res;
while (res = getchar(), !isalpha(res))
;
while (ch = getchar(), isalpha(ch))
;
return res;
}
int main() {
bool flag = true;
char ch;
int i, x, y, q;
while (~scanf("%d", &n)) {
if (flag)
flag = false;
else
putchar('\n');
lct.Init();
for (i = 1; i <= n; i++)
lct.bef[i] = INT();
q = INT();
while (q--) {
ch = CHAR();
if (ch == 'Q') {
x = INT();
printf("%d\n", lct.Query(x));
} else {
x = INT(), y = INT();
lct.Join(x, y);
}
}
}
return 0;
}
题目大意:指定一颗树上有3个操作:询问操作,询问a点和b点之间的路径上最长的那条边的长度;取反操作,将a点和b点之间的路径权值都取相反数;变化操作,把某条边的权值变成指定的值。
#include<cstdio>
#include<cstring>
#include<queue>
#include<iostream>
#include<algorithm>
#define MAXN 100010
#define MAXM 200010
#define oo 0x7FFFFFFF
using namespace std;
bool vis[MAXN];
int first[MAXN], next[MAXM], v[MAXM], cost[MAXM], e;
struct LCT {
bool neg[MAXN];
int maxi[MAXN], mini[MAXN];
int bef[MAXN], belong[MAXN];
int next[MAXN][2], pre[MAXN], key[MAXN];
void Init() {
memset(next, 0, sizeof(next));
memset(pre, 0, sizeof(pre));
memset(neg, false, sizeof(neg));
maxi[0] = -oo;
mini[0] = oo;
}
inline void PushUp(int x) {
maxi[x] = key[x];
if (next[x][0])
maxi[x] = max(maxi[x], maxi[next[x][0]]);
if (next[x][1])
maxi[x] = max(maxi[x], maxi[next[x][1]]);
mini[x] = key[x];
if (next[x][0])
mini[x] = min(mini[x], mini[next[x][0]]);
if (next[x][1])
mini[x] = min(mini[x], mini[next[x][1]]);
}
inline void Not(int x) {
if (x) {
neg[x] ^= true;
swap(maxi[x], mini[x]);
key[x] = -key[x];
maxi[x] = -maxi[x];
mini[x] = -mini[x];
}
}
inline void PushDown(int x) {
if (neg[x]) {
Not(next[x][0]);
Not(next[x][1]);
neg[x] = false;
}
}
void Rotate(int x, int kind) {
int y, z;
y = pre[x];
z = pre[y];
PushDown(y);
PushDown(x);
next[y][!kind] = next[x][kind];
pre[next[x][kind]] = y;
next[z][next[z][1] == y] = x;
pre[x] = z;
next[x][kind] = y;
pre[y] = x;
PushUp(y);
}
void Splay(int x) {
int rt;
for (rt = x; pre[rt]; rt = pre[rt])
;
if (rt != x) {
bef[x] = bef[rt];
bef[rt] = 0;
PushDown(x);
while (pre[x]) {
if (next[pre[x]][0] == x)
Rotate(x, 1);
else
Rotate(x, 0);
}
PushUp(x);
}
}
void Access(int x) {
int father;
for (father = 0; x; x = bef[x]) {
Splay(x);
PushDown(x);
pre[next[x][1]] = 0;
bef[next[x][1]] = x;
next[x][1] = father;
pre[father] = x;
bef[father] = 0;
father = x;
PushUp(x);
}
}
void Change(int x, int y) {
int t;
t = belong[x - 1];
Splay(t);
key[t] = y;
}
void Negate(int x, int y) {
Access(y);
for (y = 0; x; x = bef[x]) {
Splay(x);
if (!bef[x]) {
Not(y);
Not(next[x][1]);
return;
}
PushDown(x);
pre[next[x][1]] = 0;
bef[next[x][1]] = x;
next[x][1] = y;
pre[y] = x;
bef[y] = 0;
y = x;
PushUp(x);
}
}
int Query(int x, int y) {
Access(y);
for (y = 0; x; x = bef[x]) {
Splay(x);
if (!bef[x])
return max(maxi[y], maxi[next[x][1]]);
PushDown(x);
pre[next[x][1]] = 0;
bef[next[x][1]] = x;
next[x][1] = y;
pre[y] = x;
bef[y] = 0;
y = x;
PushUp(x);
}
return 0;
}
} lct;
int INT() {
char ch;
int res;
bool neg;
while (ch = getchar(), !isdigit(ch) && ch != '-')
;
if (ch == '-') {
res = 0;
neg = true;
} else {
res = ch - '0';
neg = false;
}
while (ch = getchar(), isdigit(ch))
res = res * 10 + ch - '0';
return neg ? -res : res;
}
char CHAR() {
char ch, res;
while (res = getchar(), !isalpha(res))
;
while (ch = getchar(), isalpha(ch))
;
return res;
}
inline void AddEdge(int x, int y, int val) {
v[e] = y;
cost[e] = val;
next[e] = first[x];
first[x] = e++;
}
void BFS(int x) {
int i, y;
queue<int> q;
memset(vis, false, sizeof(vis));
vis[x] = true;
q.push(x);
while (!q.empty()) {
x = q.front();
q.pop();
for (i = first[x]; i != -1; i = next[i]) {
y = v[i];
if (!vis[y]) {
lct.bef[y] = x;
lct.key[y] = cost[i];
lct.belong[i >> 1] = y;
vis[y] = true;
q.push(y);
}
}
}
}
int main() {
int c, i;
char ch;
int n, x, y, val;
c = INT();
while (c--) {
n = INT();
lct.Init();
memset(first, -1, sizeof(first));
for (e = 0, i = 1; i < n; i++) {
x = INT(), y = INT(), val = INT();
AddEdge(x, y, val);
AddEdge(y, x, val);
}
BFS(1);
while (ch = CHAR(), ch != 'D') {
x = INT(), y = INT();
if (ch == 'Q')
printf("%d\n", lct.Query(x, y));
else if (ch == 'C')
lct.Change(x, y);
else
lct.Negate(x, y);
}
}
return 0;
}