树链剖分模版

博客介绍了树链剖分的基础概念和应用,通过spoj375题目进行实例分析,探讨了树链剖分中重边和轻边的性质,并证明了在任意两点间链上重边和轻边数目不超过logn。同时,讨论了树链剖分在线段树中的应用,以及查询和修改操作的时间复杂度为logn*logn。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目:spoj375

树链剖分训练题目

题意:给定一棵n节点的树,有两种操作,①修改某一条边的权值②查询某一条链u-->v的权值最大的边。

分析:树链剖分入门学习

对树链剖分的初步认识:

①把树上的边分为两类:重边轻边。

②任意两个节点u和v连接的这条链上,重链和轻边的数目都不超过logn(n为总的边数)

很明显,以某个轻节点为根的子树的规模不会超过父亲节点的规模的一半,否则就不是轻节点了。

1.在链u--->v上,轻边数目不超过logn

瞎证明:

lca=LCA(u,v)。

首先证明u-->lca的链上,轻边数目不超过logn。在极端情况下,链上的边全部为轻边。

由于u--->lca的这条链上以每个点为根节点的子树的大小最多为父亲节点的一半,从lca开始往下走,每次节点的数目都会最少减少一半,所以最多减少logn次就会走到底。

同理证明v--->lca的链上,轻边数目不超过logn。

所以,在链u--->v上,轻边数目不超过2*logn.....

2.在链u--->v上,重链的数目不超过logn

瞎证明:

u--->v这条链可以看成一条链有两种颜色(就是重边和轻边),重边为黑,轻边为白。//白边把黑边分成很多段

初始链全部为黑色,现在如何将链涂色,使得黑边的数目最多,明显就是让白色交错在里面,而白色的边最多logn条,所以黑色的链最多也是logn条。


任意两个节点u和v连接的这条链上,一条重链上重边在线段树里面的编号是连续的,因此可以在线段树里面O(logn)访问。(从u到top[u]这条重链)

任意两个节点u和v连接的这条链上,轻边在线段树里面的编号不一定连续。

⑤查询和修改的u-->v这条链的时间复杂度为logn*logn

代码:

#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <math.h>
#include <stdlib.h>
using namespace std;

#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1 
const int INF = 2147483647;
const int MAXN = 10010;
struct node
{
    int v,next;
}List[MAXN*2];
int head[MAXN],cnt;
int top[MAXN];//top[v]表示v所在的重链的顶端节点
int fa[MAXN]; //父亲节点
int deep[MAXN];//深度
int sz[MAXN];//sz[v]表示以v为根的子树的节点数
int p[MAXN];//p[v]表示v与其父亲节点的连边在线段树中的位置
int fp[MAXN];//和p数组相反
int son[MAXN];//重儿子
int pos;
void init()
{
    cnt = 0;
    memset(head,-1,sizeof(head));
    pos = 0;
    memset(son,-1,sizeof(son));memset(fa,-1,sizeof(fa));
}
void add(int u,int v)
{
    List[cnt].v=v;
    List[cnt].next=head[u];
    head[u]=cnt++;
}

int dfs1(int cur,int dp)  //第一遍dfs求出fa,deep,sz,son
{
	deep[cur]=dp;
	sz[cur]=1;
	son[cur]=-1;
	for(int i=head[cur];~i;i=List[i].next)
	{
		int to=List[i].v;
		if(fa[cur]!=to)
		{
			fa[to]=cur;
			sz[cur]+=dfs1(to,dp+1);
			if(son[cur]==-1 || sz[to]>sz[son[cur]])
				son[cur]=to; 
		}
	}
	return sz[cur];
}
void dfs2(int cur,int sp)  //第二遍dfs求出top和p
{
	top[cur]=sp;
	p[cur]=pos++;
	fp[p[cur]]=cur;
	if(son[cur]==-1)
		return ;
	dfs2(son[cur],sp);
	for(int i=head[cur];~i;i=List[i].next)
	{
		int to=List[i].v;
		if(to!=son[cur] && fa[cur]!=to)
			dfs2(to,to);
	}
}

//线段树
int tree[MAXN<<4];
void update(int pos,int v,int l,int r,int rt)
{
	if(l==r)
	{
		tree[rt]=v;
		return ;
	}
	int m=(l+r)>>1;
	if(pos<=m)
		update(pos,v,lson);
	else
		update(pos,v,rson);
	tree[rt]=max(tree[rt<<1],tree[rt<<1|1]); 
}
int query(int L,int R,int l,int r,int rt)
{
	if(L<=l && r<=R)
		return tree[rt];
	int m=(l+r)>>1,fg1=-INF,fg2=-INF;
	if(L<=m)
		fg1=query(L,R,lson);
	if(R>m)
		fg2=query(L,R,rson);
	return max(fg1,fg2);
}
int Q(int L,int R)
{
	return query(L,R,1,pos,1);
}
int find(int u,int v)//查询u->v边的最大值
{
    int f1 = top[u], f2 = top[v];
    int tmp = 0;
    while(f1 != f2)
    {
        if(deep[f1] < deep[f2])
        {
            swap(f1,f2);
            swap(u,v);
        }
        tmp = max(tmp,Q(p[f1],p[u])); //f1在线段树里面的编号比u小 
        u = fa[f1]; f1 = top[u];
    }
    if(u == v)return tmp;
    if(deep[u] > deep[v]) swap(u,v);
    return max(tmp,Q(p[son[u]],p[v]));
}
int e[MAXN][3];
int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    int T;
    int n;
    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]);
            add(e[i][0],e[i][1]);
            add(e[i][1],e[i][0]);
        }
        dfs1(1,1);
        dfs2(1,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(p[e[i][1]],e[i][2],1,pos,1);
        }
        char op[10];
        int u,v;
        while(scanf("%s",op) == 1)
        {
            if(op[0] == 'D')break;
            scanf("%d%d",&u,&v);
            if(op[0] == 'Q')
                printf("%d\n",find(u,v));
            else update(p[e[u-1][1]],v,1,pos,1);
        }
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值