樹鏈剖分模板+入門題 SPOJ - QTREE
題目連結:[點選進入](http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=13013)
樹鏈剖分並不是一個複雜的演算法或者資料結構,只是能把一棵樹拆成鏈來處理而已,換一種說法,樹鏈剖分只是xxx資料結構/演算法在樹上的推廣,或者說,樹鏈剖分只是把樹hash到了幾段連續的區間上。比如說下面這道題,就是將樹分為重鏈和輕鏈然後對映到線段樹上,然後再線上段樹上進行查詢和修改等操作。所以樹鏈剖分的重點有兩個,一是正確的將樹分解成幾段並對映到線段樹上去,二是線上段樹中進行查詢和修改操作時要注意藉助分解後樹的性質。
下面這份程式碼是針對查詢修改樹上的邊的,可以作為模板使用。
一篇講的非常好的部落格:樹鏈剖分
程式碼如下:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
///基於邊權,修改單條邊
///查詢路徑邊權最大值
const int maxn=10010;
struct Edge
{
int to,next;
}edge[maxn*2];
int head[maxn],tot;
int top[maxn];///top[v]表示v所在鏈的頂端結點
int fa[maxn]; ///父節點
int deep[maxn]; ///深度
int num[maxn]; ///num[v]表示以v為根的子樹的結點樹
int p[maxn];///p[v]表示v與其父節點的連邊線上段樹中的位置
int fp[maxn]; ///表示線段樹中的某個位置對應的邊的起始編號
int son[maxn]; ///son[u]表示u的重兒子
int pos;
void init()
{
tot=0;
memset(head,-1,sizeof(head));
pos=0;
memset(son,-1,sizeof(son));
}
///模擬鄰接表
void addedge(int u,int v)
{
edge[tot].to=v;
edge[tot].next=head[u];
head[u]=tot++;
}
///第一遍dfs求出fa,num,deep,son等值
void dfs1(int u,int pre,int d)
{
deep[u]=d;
fa[u]=pre;
num[u]=1;
for(int i=head[u];i!=-1;i=edge[i].next)
{
int v=edge[i].to;
if(v!=pre)
{
dfs1(v,u,d+1);
num[u]+=num[v];
///確定u的重兒子
if(son[u]==-1||num[v]>num[son[u]])
son[u]=v;
}
}
}
///第二遍dfs求出top和p
void getpos(int u,int sp)
{
top[u]=sp;
p[u]=pos++;
fp[p[u]]=u;
if(son[u]==-1) return;
///保證重鏈上重邊線上段樹中的連續分佈
getpos(son[u],sp);
for(int i=head[u];i!=-1;i=edge[i].next)
{
int v=edge[i].to;
if(v!=son[u]&&v!=fa[u]) ///對其它不是重邊的結點的處理
getpos(v,v);
}
}
///線段樹
struct node
{
int l,r;
int Max;
}segTree[maxn*3];
void build(int i,int l,int r)
{
segTree[i].l=l;
segTree[i].r=r;
segTree[i].Max=0;
if(l==r) return;
int mid=(l+r)/2;
build(i<<1,l,mid);
build((i<<1)|1,mid+1,r);
}
void push_up(int i)
{
segTree[i].Max=max(segTree[i<<1].Max,segTree[(i<<1)|1].Max);
}
///單點更新k值為val
void update(int i,int k,int val)
{
if(segTree[i].l==k&&segTree[i].r==k)
{
segTree[i].Max=val;
return;
}
int mid=(segTree[i].l+segTree[i].r)/2;
if(k<=mid)
update(i<<1,k,val);
else
update((i<<1)|1,k,val);
push_up(i);
}
///區間查詢:[l,r]中的最大值
int query(int i,int l,int r)
{
if(segTree[i].l==l&&segTree[i].r==r)
return segTree[i].Max;
int mid=(segTree[i].l+segTree[i].r)/2;
if(r<=mid) return query(i<<1,l,r);
else if(l>mid) return query((i<<1)|1,l,r);
else return max(query(i<<1,l,mid),query((i<<1)|1,mid+1,r));
}
///查詢u->v邊的最大值
int find(int u,int v)
{
int f1=top[u];
int f2=top[v];
int tmp=0;
while(f1!=f2)
{
///總是保證deep[f1]>=deep[f2]
if(deep[f1]<deep[f2])
{
swap(f1,f2);
swap(u,v);
}
tmp=max(tmp,query(1,p[f1],p[u]));
u=fa[f1]; f1=top[u];
}
if(u==v) return tmp;
if(deep[u]>deep[v])
swap(u,v);
return max(tmp,query(1,p[son[u]],p[v]));
}
int e[maxn][3];
int main()
{
int T;
int n,u,v;
// freopen("in.txt","r",stdin);
scanf("%d",&T);
while(T--)
{
init();
scanf("%d",&n);
for(int i=0;i<n-1;i++)
{
scanf("%d%d%d",&e[i][0],&e[i][1],&e[i][2]);
///注意新增的是雙向邊
addedge(e[i][0],e[i][1]);
addedge(e[i][1],e[i][0]);
}
dfs1(1,0,0);
getpos(1,1);
build(1,0,pos-1);
///下面是利用每條邊更新線段樹的操作
for(int i=0;i<n-1;i++)
{
if(deep[e[i][0]]>deep[e[i][1]])
swap(e[i][0],e[i][1]);
update(1,p[e[i][1]],e[i][2]);
}
char op[10];
while(scanf("%s",op))
{
if(op[0]=='D') break;
scanf("%d%d",&u,&v);
if(op[0]=='Q')
printf("%d\n",find(u,v)); ///查詢
else
update(1,p[e[u-1][1]],v); ///修改
}
}
return 0;
}
相關文章
- 【筆記/模板】樹鏈剖分筆記
- #8. 「模板」樹鏈剖分
- 樹鏈剖分
- 長鏈剖分模板
- spoj375 樹鏈剖分(單點更新,區間查詢)
- [OI] 樹鏈剖分
- 對樹鏈剖分的愛 題解
- 樹鏈剖分總結
- 淺談樹鏈剖分
- 樹鏈剖分學習筆記筆記
- 「學習筆記」樹鏈剖分筆記
- codechef Dynamic GCD [樹鏈剖分 gcd]GC
- 2024.3.14 樹鏈剖分
- P8025 【樹鏈剖分求祖先】
- BF的資料結構題單-提高組——樹鏈剖分資料結構
- 樹鏈剖分解析
- 重鏈剖分題目選講
- 一起來學習樹鏈剖分吧!
- 長鏈剖分筆記筆記
- bzoj4034: [HAOI2015]樹上操作(樹鏈剖分+線段樹)
- TZOJ 8472 : Tree (重鏈剖分+線段樹) POJ 3237
- poj 3237 樹鏈剖分(區間更新,區間查詢)
- HYSBZ 2243 樹鏈剖分(區間更新,區間查詢)較難
- 樹剖(不太會)
- 【2024-ZR-C Day 6】資料結構(4):樹(重、長)鏈剖分、虛樹、dsu on tree資料結構
- bzoj3626: [LNOI2014]LCA(離線處理+樹鏈剖分)
- hud--4251The Famous ICPC Team Again+劃分樹入門題AI
- tendermint區塊鏈五分鐘入門區塊鏈
- 【模板】樹分塊(簡單版)
- SPOJ TTM To the moon(主席樹+區間操作)
- hihocoder 1232 || 2015北京網路賽F題 樹連剖分
- jqueryEasyUi 入門模板jQueryUI
- 線段樹+差分——【模板】樹狀陣列2陣列
- 左偏樹入門
- 『dfn、樹剖雜項』Day9
- vue 快速入門 系列 —— 模板Vue
- jfinal enjoy模板入門
- 線段樹入門