[JLOI2014]松鼠的新家————树链剖分

题解:本题主要考查树链剖分。
简要题意:一颗树,有n个房间,并且有n-1根树枝连接。小熊维尼先去 a 1 a_1 a1,再去 a 2 a_2 a2最后到 a n a_n an,每走到一个房间,他就可以从房间拿一块糖果吃,最后一个房间不用,求每个房间至少需要放多少个糖果。
1.树链剖分:路过的点都加一,可以想到树链剖分,每一次将 a [ i ] a[i] a[i] a [ i + 1 ] a[i+1] a[i+1]的节点加一,在一次修改后,因为最后一个房间不用加,所以避免重复终点要减1。线段树模板区间加和区间查询。代码还是比较清楚的。
注意:本题数据较大,要用高效的读写(一般做这些题都要吧QwQ),数组一定要开四倍!
代码如下:

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
struct N
{
	int ans,lazy;
}t[1126666];
struct E
{
	int start,to;
}e[1266666];
int a[1266666],h[1266666],siz[1266666],son[1266666],d[1266666];
int id[1266666],rk[1266666],top[1266666],f[1266666];
int P,num,n,m;
void pushup(int p)
{
	t[p].ans=t[p*2].ans+t[p*2+1].ans;
	return ;
}
void build(int p,int l,int r)
{
	if(l==r){t[p].ans=a[l];return ;}
	int mid=l+r>>1;
	build(p<<1,l,mid);
	build(p<<1|1,mid+1,r);
	pushup(p);
}
void lai(int p,int l,int r)
{
	int mid=l+r>>1;
	if(t[p].lazy)
	{
		t[p*2].ans+=t[p].lazy*(mid-l+1);
		t[p*2+1].ans+=t[p].lazy*(r-mid);
		t[p*2].lazy+=t[p].lazy;
		t[p*2+1].lazy+=t[p].lazy;
		t[p].lazy=0;
	}
	return ;
}
void change(int p,int l,int r,int x,int y,int z)
{
	if(x<=l&&y>=r)
	{
		t[p].ans+=z*(r-l+1);
		t[p].lazy+=z;
		return ;
	}
	lai(p,l,r);
	int mid=l+r>>1;
	if(x<=mid)change(p<<1,l,mid,x,y,z);
	if(y>mid)change(p<<1|1,mid+1,r,x,y,z);
	pushup(p);
}
long long ask(int p,int l,int r,int x,int y)
{
	long long ans=0;
	if(x<=l&&y>=r)return t[p].ans;
	lai(p,l,r);
	int mid=l+r>>1;
	if(x<=mid)ans+=ask(p<<1,l,mid,x,y);
	if(y>mid)ans+=ask(p<<1|1,mid+1,r,x,y);
	return ans;
}
void add(int start,int to)
{
	e[++P].to=to;
	e[P].start=h[start];
	h[start]=P;
}
void dfs1(int p,int fa,int deep)
{
    f[p]=fa;siz[p]=1;d[p]=deep;
    for(int i=h[p];i;i=e[i].start)
	{
        int k=e[i].to;
        if(k==fa)continue;
        dfs1(k,p,deep+1);
        siz[p]+=siz[k];
        if(siz[k]>siz[son[p]])son[p]=k;
    }
}
void dfs2(int p,int tp)
{
	id[p]=++num;rk[num]=a[p];top[p]=tp;
    if(!son[p])return;
    dfs2(son[p],tp);
    for(int i=h[p];i;i=e[i].start)
	{
        int k=e[i].to;
        if(k==f[p]||k==son[p]) continue;
        dfs2(k,k);
    }
}
int sum(int x,int y,int z)
{
	while(top[x]!=top[y])
	{
		if(d[top[x]]<d[top[y]])swap(x,y);
		change(1,1,n,id[top[x]],id[x],z);
		x=f[top[x]];
	}
	if(d[x]>d[y])swap(x,y);
	change(1,1,n,id[x],id[y],z);
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)cin>>a[i];
	for(int i=1;i<=n-1;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		add(x,y);add(y,x);
	}
	//build(1,1,n);
	dfs1(1,0,1);dfs2(1,1);
	for(int i=1;i<=n-1;i++)
	{
        sum(a[i],a[i+1],1);
        sum(a[i+1],a[i+1],-1);
    }
    for(int i=1;i<=n;i++)
    printf("%d\n",ask(1,1,n,id[i],id[i]));
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值