原文地址:树链剖分[若侵权,请私信,必删]
“在一棵树上进行路径的修改、求极值、求和”乍一看只要线段树就能轻松解决,实际上,仅凭线段树是不能搞定它的。我们需要用到一种貌似高级的复杂算法——树链剖分。
**数据规模大时,递归可能会爆栈,而非递归dfs会很麻烦,所以可将两个dfs改为宽搜+循环。即先宽搜求出fa、dep,然后逆序循环求出siz、son,再顺序循环求出top和w。
#include <cstdio>
#include <algorithm>
#include <iostream>
#include <string.h>
using namespace std;
const int maxn = 10010;
struct Tedge
{ int b, next; } e[maxn * 2];
int tree[maxn];
int zzz, n, z, edge, root, a, b, c;
int d[maxn][3];
int first[maxn], dep[maxn], w[maxn], fa[maxn], top[maxn], son[maxn], siz[maxn];
char ch[10];
void insert(int a, int b, int c)
{
e[++edge].b = b;
e[edge].next = first[a];
first[a] = edge;
}
void dfs(int v)
{
siz[v] = 1; son[v] = 0;
for (int i = first[v]; i > 0; i = e[i].next)
if (e[i].b != fa[v])
{
fa[e[i].b] = v;
dep[e[i].b] = dep[v]+1;
dfs(e[i].b);
if (siz[e[i].b] > siz[son[v]]) son[v] = e[i].b;
siz[v] += siz[e[i].b];
}
}
void build_tree(int v, int tp)
{
w[v] = ++ z; top[v] = tp;
if (son[v] != 0) build_tree(son[v], top[v]);
for (int i = first[v]; i > 0; i = e[i].next)
if (e[i].b != son[v] && e[i].b != fa[v])
build_tree(e[i].b, e[i].b);
}
void update(int root, int lo, int hi, int loc, int x)
{
if (loc > hi || lo > loc) return;
if (lo == hi)
{ tree[root] = x; return; }
int mid = (lo + hi) / 2, ls = root * 2, rs = ls + 1;
update(ls, lo, mid, loc, x);
update(rs, mid+1, hi, loc, x);
tree[root] = max(tree[ls], tree[rs]);
}
int maxi(int root, int lo, int hi, int l, int r)
{
if (l > hi || r < lo) return 0;
if (l <= lo && hi <= r) return tree[root];
int mid = (lo + hi) / 2, ls = root * 2, rs = ls + 1;
return max(maxi(ls, lo, mid, l, r), maxi(rs, mid+1, hi, l, r));
}
inline int find(int va, int vb)
{
int f1 = top[va], f2 = top[vb], tmp = 0;
while (f1 != f2)
{
if (dep[f1] < dep[f2])
{ swap(f1, f2); swap(va, vb); }
tmp = max(tmp, maxi(1, 1, z, w[f1], w[va]));
va = fa[f1]; f1 = top[va];
}
if (va == vb) return tmp;
if (dep[va] > dep[vb]) swap(va, vb);
return max(tmp, maxi(1, 1, z, w[son[va]], w[vb])); //
}
void init()
{
scanf("%d", &n);
root = (n + 1) / 2;
fa[root] = z = dep[root] = edge = 0;
memset(siz, 0, sizeof(siz));
memset(first, 0, sizeof(first));
memset(tree, 0, sizeof(tree));
for (int i = 1; i < n; i++)
{
scanf("%d%d%d", &a, &b, &c);
d[i][0] = a; d[i][1] = b; d[i][2] = c;
insert(a, b, c);
insert(b, a, c);
}
dfs(root);
build_tree(root, root); //
for (int i = 1; i < n; i++)
{
if (dep[d[i][0]] > dep[d[i][1]]) swap(d[i][0], d[i][1]);
update(1, 1, z, w[d[i][1]], d[i][2]);
}
}
inline void read()
{
ch[0] = ' ';
while (ch[0] < 'C' || ch[0] > 'Q') scanf("%s", &ch);
}
void work()
{
for (read(); ch[0] != 'D'; read())
{
scanf("%d%d", &a, &b);
if (ch[0] == 'Q') printf("%d\n", find(a, b));
else update(1, 1, z, w[d[a][1]], b);
}
}
int main()
{
for (scanf("%d", &zzz); zzz > 0; zzz--)
{
init();
work();
}
return 0;
}
附上博主的丑代码=-=:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
#define maxn 50008
#define maxm 100008
using namespace std;
inline void read(int &x){
char ch;
bool flag=false;
for (ch=getchar();!isdigit(ch);ch=getchar())if (ch=='-') flag=true;
for (x=0;isdigit(ch);x=x*10+ch-'0',ch=getchar());
x=flag?-x:x;
}
inline void write(int x){
static const int maxlen=100;
static char s[maxlen];
if (x<0) { putchar('-'); x=-x;}
if(!x){ putchar('0'); return; }
int len=0; for(;x;x/=10) s[len++]=x % 10+'0';
for(int i=len-1;i>=0;--i) putchar(s[i]);
}
//pos指该点到父亲的边在边序列的编号,得到边序列后便可用数据结构来处理
//top指该点所在重链的起点
//fa 该点的父亲节点, son该点的重儿子 ,size 该点的子树的节点数
int fa[maxn],son[maxn],deep[maxn],top[maxn],size[maxn],pos[maxn];
int kk[maxn][8];//记录边
int ed[maxm],pre[maxm],now[maxn],v[maxm],tot;
void build(int a,int b,int c){
pre[++tot]=now[a];
now[a]=tot;
ed[tot]=b;
v[tot]=c;
}
int sum;//sum指序列总大小;
void dfs(int x){
size[x]=1; son[x]=0;
for (int p=now[x];p;p=pre[p])
if (ed[p]!=fa[x])
{
int y=ed[p];
fa[y]=x; deep[y]=deep[x]+1;
dfs(y);
if (size[y]>size[son[x]]) son[x]=y;
size[x]+=size[y];
}
}//第一遍求deep,size,son
void dfs2(int x,int topp){
pos[x]=++sum; top[x]=topp;
if (son[x])
dfs2(son[x],topp);
for (int p=now[x];p;p=pre[p])
if (ed[p]!=fa[x])
if (ed[p]!=son[x])
dfs2(ed[p],ed[p]);
}//第二遍求pos,top
int tree[maxn*7];
void change(int l,int r,int p,int a,int b){
if ((l==a)&&(r==a))
{
tree[p]=b;
return;
}
int mid=(l+r)/2;
if (a<=mid)
change(l,mid,p*2,a,b);
else
change(mid+1,r,p*2+1,a,b);
tree[p]=max(tree[p*2],tree[p*2+1]);
}
int find(int l,int r,int p,int a,int b){
if ((l==a)&&(r==b))
return tree[p];
int mid=(l+r)/2;
if (b<=mid)
return find(l,mid,p*2,a,b);
else
if (a>mid)
return find(mid+1,r,p*2+1,a,b);
else
return max(find(l,mid,p*2,a,mid),find(mid+1,r,p*2+1,mid+1,b));
}
int get(int a,int b){
int f1=top[a],f2=top[b],ans=0;
while (f1!=f2){
if(deep[f1]<deep[f2])
{
swap(f1,f2);
swap(a,b);
}
ans=max(ans,find(1,sum,1,pos[f1],pos[a]));
a=fa[f1];f1=top[a];
}
if(a==b) return ans;
if (deep[a]>deep[b]) swap(a,b);
return max(ans,find(1,sum,1,pos[son[a]],pos[b]));
}//每次取深度大的点向上跳,直到两点在同一重链
int n;
void init(){
read(n);
memset(now,0,sizeof(int)*(n+1));
memset(tree,0,sizeof(int)*(n+1)*7);
int root =1;
tot=0; sum=0;deep[root]=0;fa[root]=0;
for (int i=1;i<n;i++){
int a,b,c;
read(a); read(b); read(c);
kk[i][0]=a; kk[i][1]=b; kk[i][2]=c;
build(a,b,c);
build(b,a,c);
}
dfs(root);
dfs2(root,root);
for (int i=1;i<n;i++){
if (deep[kk[i][0]]>deep[kk[i][1]])
swap(kk[i][0],kk[i][1]);//保证每条边均由父亲节点指向子节点
change(1,sum,1,pos[kk[i][1]],kk[i][2]);
}
}
char ch[200];
void rreadd(){
ch[0]=' ';
while (ch[0] < 'C' || ch[0] > 'Q') scanf("%s", &ch);
}
void doit(){
for (rreadd();ch[0]!='D';rreadd())
{
int a,b;
read(a); read(b);
if (ch[0]=='Q')
{ write(get(a,b)); puts("");}
else
change(1,sum,1,pos[kk[a][1]],b);
}
}
int main(){
int ca;
read(ca);
for (int i=1;i<=ca;i++)
{
init();
doit();
}
return 0;
}