树链剖分--解题报告

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3966

题目描述:树链剖分模板题,给你一颗树,有三种操作:

                  I: c1  到 c2 的点加上k

                  D:c1 到 c2 的点减去k

                  Q:查询某个点的值

题目做法:进行树链剖分然后就是线段树的操作,为了避免炸内存,图的存储用链式前向星进行优化。

链式前向星学习链接:https://blog.youkuaiyun.com/lookqaq/article/details/81304637

链式前向星:head[i]表示以i为起点的最后一条边的位置,next表示与第i条边同起点的上一条边的位置,to表示第i条边的终点。

树链剖分学习链接:https://www.cnblogs.com/ivanovcraft/p/9019090.html

顺便复习一下树链剖分:树链剖分就是将树进行轻重链剖分,从而转化为区间问题,运用线段树或者树状数组高效处理区间问题。主要代码是两遍dfs,第一遍dfs可以得到当前节点的父亲结点(fa数组)、当前结点的深度值(d数组)、当前结点的子结点数量(size数组)、当前结点的重儿子(son数组)。第二遍dfs的时候则可以将各个重结点连接成重链,轻节点连接成轻链,并且将重链(其实就是一段区间)用数据结构(一般是树状数组或线段树)来进行维护,并且为每个节点进行编号,其实就是dfs在执行时的顺序(num_id数组),以及当前节点所在链的起点(top数组),还有当前节点在树中的位置(id_num数组)。

复杂度分析:可以证明树链剖分的时间复杂度为O(nlog^2n)。

代码如下:

#include<bits/stdc++.h>
using namespace std;

const int maxn=5e4+10;
struct node{
	int l;
	int r;
	int lazy;
	int sum;
}tree[maxn<<2];
struct edge{
	int next;
	int to;
}e[maxn*2];
int v[maxn],head[maxn],id_num[maxn],num_id[maxn],top[maxn],size[maxn],fa[maxn],son[maxn],d[maxn],cnt;

void add(int u,int v){
	e[++cnt].next = head[u];
	e[cnt].to = v;
	head[u] = cnt;
}

void dfs1(int now,int f,int depth){
	d[now] = depth;
	size[now] = 1;
	fa[now] = f;
	for(int i = head[now];i;i = e[i].next){
		int v = e[i].to;
		if(v != f){
			dfs1(v,now,depth+1);
			size[now] += size[v];
			if(size[v] > size[son[now]]){
				son[now] = v;
			}
		}
	}
}

void dfs2(int now,int rt){
	top[now] = rt;
	id_num[++cnt] = now;
	num_id[now] = cnt;
	if(!son[now])return ;
	dfs2(son[now],top[now]);
	for(int i = head[now];i;i = e[i].next){
		int v = e[i].to; 
		if(v != son[now] && v != fa[now]){
			dfs2(v,v);
		}
	}
}

void pushup(int rt){
	tree[rt].sum = tree[rt*2].sum + tree[rt*2+1].sum;
}

void pushdown(int rt){
	if(tree[rt].lazy){
		tree[rt*2].lazy += tree[rt].lazy;
		tree[rt*2+1].lazy += tree[rt].lazy;
		tree[rt*2].sum += (tree[rt*2].r-tree[rt*2].l+1)*tree[rt].lazy;
		tree[rt*2+1].sum += (tree[rt*2+1].r-tree[rt*2+1].l+1)*tree[rt].lazy;
		tree[rt].lazy = 0;
	}
}

void build(int rt,int l,int r){
	if(l == r){
		tree[rt].l = tree[rt].r = l;
		tree[rt].lazy = 0;
		tree[rt].sum = v[id_num[l]];
		return ;
	}
	int mid = (l+r)/2;
	tree[rt].r = r,tree[rt].l = l,tree[rt].lazy = 0;
	build(rt*2,l,mid);build(rt*2+1,mid+1,r);
	pushup(rt);
} 

void update(int l,int r,int c,int rt){
	if(tree[rt].r < l||tree[rt].l > r)return ;
	if(tree[rt].l >= l&&tree[rt].r <= r){
		tree[rt].sum +=(tree[rt].r-tree[rt].l+1)*c;
		tree[rt].lazy += c;
		return ;
	}
	pushdown(rt);
	int mid = (tree[rt].l+tree[rt].r)/2;
	if(l <= mid)update(l,r,c,rt*2);
	if(mid < r) update(l,r,c,rt*2+1);
}

int query(int l,int r,int rt){
	int res = 0;
	if(tree[rt].r < l||tree[rt].l > r)return 0;
	if(tree[rt].l >= l&&tree[rt].r <= r){
		return tree[rt].sum;
	}
	pushdown(rt);
	int mid = (tree[rt].l+tree[rt].r)/2;
	if(l <= mid) res += query(l,r,rt*2);
	if(mid < r) res += query(l,r,rt*2+1);
	return res;
} 

void updates(int x,int y,int c){
	while(top[x] != top[y]){
		if(d[top[x]] < d[top[y]]){
			update(num_id[top[y]],num_id[y],c,1);
			y = fa[top[y]];
		}
		else{
			update(num_id[top[x]],num_id[x],c,1);
			x = fa[top[x]];
		} 
	}
	if(d[x] < d[y]){
		swap(x,y);
	}
	update(num_id[y],num_id[x],c,1);
}

void init(){
	memset(head,0,sizeof(head));
	memset(son,0,sizeof(son));
	cnt = 0;
}

int main(){
	int n,m,p;
	while(scanf("%d%d%d",&n,&m,&p) != EOF){
		init();
	    for(int i = 1;i <= n;++i)
	    {
	        scanf("%d",&v[i]);
	    }
	    int from,to;
	    for(int i = 1;i <= m;++i)
	    {
	        scanf("%d%d",&from,&to);
			add(from,to);
	        add(to,from);
	    }
	    dfs1(1,1,1);
	    cnt = 0;dfs2(1,1);
	    //for(int i = 1;i <= n; i++)printf("%d ",id_num[i]);
	    build(1,1,n);
	    char op[2];
	    int x,y,z;
	    for(int i = 0;i < p;++i)
	    {
	        scanf("%s",op);
	        if(op[0] == 'I')
	        {
	            scanf("%d%d%d",&x,&y,&z);
	            updates(x,y,z);
	        }
	        else if(op[0] == 'D')
	        {
	            scanf("%d%d%d",&x,&y,&z);
	            updates(x,y,-z);
	        }
	        else
	        {
	            scanf("%d",&x);
	            printf("%d\n",query(num_id[x],num_id[x],1));
	        }
	    }	    	
	}	
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值