牛客练习赛61 苹果树

链接

点击跳转

题解

动态树分治

这个名字虽然看上去貌似挺吓人的,其实原理很简单

就是你点分治的时候不是先找到一个重心 G G G吗,然后你利用这个重心 G G G把树分成了好几块,接下来在每个子树里面再去找重心 g 1 , g 2 , g 3 , … g_1,g_2,g_3,\dots g1,g2,g3,,那么我令 g 1 , g 2 , g 3 , … g_1,g_2,g_3,\dots g1,g2,g3,成为 G G G的孩子,这样递归建立一棵树,这个树就被称为点分树

点分树的树高是 O ( l o g n ) O(logn) O(logn)

点分树用来干啥呢?

可以想一想,我们在做点分治的时候,是不是通过枚举重心就能够遍历到所有的路径?这里我们依然利用重心的这个特性

以某个点 u u u为端点的所有路径怎么遍历呢?很简单,如果一条路径以 u u u为端点,那么这个路径肯定经过点分树中从 u u u到根节点这条链上的某个点。这一点从点分治的过程就可以看出来。

那么这个题怎么做呢?很简单,在点分树的每个点 u u u建立一个以苹果成熟度为下标的线段树,维护从 u u u到其子树(点分树的子树)中每个节点的真实距离的最小值(真实距离=在原来的树上的距离)。

修改的时候沿着点分树往上跳,一边跳一边修改,查询的时候同理。

代码

#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#define iinf 0x3f3f3f3f
#define eps 1e-8
#define maxn 100010
#define maxe 200010
#define maxk 17
#define cl(x) memset(x,0,sizeof(x))
#define rep(i,a,b) for(i=a;i<=b;i++)
#define drep(i,a,b) for(i=a;i>=b;i--)
#define em(x) emplace(x)
#define emb(x) emplace_back(x)
#define emf(x) emplace_front(x)
#define fi first
#define se second
#define de(x) cerr<<#x<<" = "<<x<<endl
using namespace std;
using namespace __gnu_pbds;
int read(int x=0)
{
	int c, f(1);
	for(c=getchar();!isdigit(c);c=getchar())if(c=='-')f=-f;
	for(;isdigit(c);c=getchar())x=x*10+c-0x30;
	return f*x;
}
struct Graph
{
	int etot, head[maxn], to[maxe], next[maxe], w[maxe];
	void clear(int N)
	{
		for(int i=1;i<=N;i++)head[i]=0;
		etot=0;
	}
	void adde(int a, int b, int c=0){to[++etot]=b;w[etot]=c;next[etot]=head[a];head[a]=etot;}
	#define forp(_,__) for(auto p=__.head[_];p;p=__.next[p])
}G;
struct Centroid_Decomposition
{
    int grey[maxn], depth[maxn], size[maxn], dist[maxn], lis[maxn], fa[maxn];
    void dfs(int pos, int pre, Graph& G)	//这个用来处理出每个点的深度
    {
        lis[++*lis]=pos;
        size[pos]=1;
        forp(pos,G)if(G.to[p]!=pre and !grey[G.to[p]])
        {
            depth[G.to[p]]=depth[pos]+1;
            dist[G.to[p]]=dist[pos]+G.w[p];
            dfs(G.to[p],pos,G);
            size[pos]+=size[G.to[p]];
        }
    }
    void findG(int pos, int pre, Graph& G, int& g, int& gsum, int nowsum, int node_tot)//找重心
    {
        if(nowsum<gsum)g=pos, gsum=nowsum;
        forp(pos,G)if(G.to[p]!=pre and !grey[G.to[p]])
            findG( G.to[p], pos, G, g, gsum, nowsum+(node_tot-2*size[G.to[p]]), node_tot );
    }
    void solve(int pos, Graph& G, int Fa)
    {
        *lis=0; depth[pos]=0; dfs(pos,0,G);
        int g=pos, gsum=0, i;
        rep(i,1,*lis)gsum+=depth[lis[i]];
        findG(pos,0,G,g,gsum,gsum,*lis);
        grey[g]=1;
        fa[g]=Fa;
        forp(g,G)if(!grey[G.to[p]])solve(G.to[p],G,g);
    }
    void run(Graph& G)
    {
        cl(grey);
        solve(1,G,0);
    }
}CD;
struct DynamicSegmentTree
{
	int pool[20000000], tot, Min[20000000], ch[20000000][2];
	int New(int v)
	{
		Min[++tot]=v;
		return tot;
	}
	int getmin(int o){return o?Min[o]:iinf;}
	void upd(int o)
	{
		Min[o] = min(getmin(ch[o][0]),getmin(ch[o][1]));
	}
	void init()
	{
		int i;rep(i,1,tot)ch[i][0]=ch[i][1]=0;
		tot=0;
	}
	int insert(int o, int pos, int v, int l, int r)
	{
		int mid(l+r>>1);
		if(!o)o=New(v);
		else Min[o]=min(Min[o],v);
		if(l==r)return o;
		if(pos<=mid)ch[o][0]=insert(ch[o][0],pos,v,l,mid);
		else ch[o][1]=insert(ch[o][1],pos,v,mid+1,r);
		return o;
	}
	int qmin(int o, int wantl, int wantr, int l, int r)
	{
		int mid(l+r>>1), ret=iinf;
		if(wantl<=l and r<=wantr)return getmin(o);
		if(wantl<=mid)ret=min(ret,qmin(ch[o][0],wantl,wantr,l,mid));
		if(wantr>mid)ret=min(ret,qmin(ch[o][1],wantl,wantr,mid+1,r));
		return ret;
	}
}segtree;
struct Doubling_LCA
{
    int f[maxn][maxk+1], depth[maxn];
    void clear(int n){for(int i=1;i<=n;i++)depth[i]=0, cl(f[i]);}
    void dfs(Graph &G, int pos, int pre)
    {
        for(auto k=1;(1<<k)<=depth[pos];k++)f[pos][k]=f[f[pos][k-1]][k-1];
        for(auto p(G.head[pos]);p;p=G.next[p])
            if(G.to[p]!=pre)
            {
                f[G.to[p]][0]=pos;
                depth[G.to[p]]=depth[pos]+1;
                dfs(G,G.to[p],pos);
            }
    }
    void run(Graph &G, int root)
    {
        depth[root]=1;
        dfs(G,root,0);
    }
    int q(int x, int y)
    {
        if(depth[x]<depth[y])swap(x,y);
        for(auto k(maxk);~k;k--)
            if(depth[f[x][k]]>=depth[y])
                x=f[x][k];
        if(x==y)return x;
        for(auto k(maxk);~k;k--)
            if(f[x][k]!=f[y][k])
                x=f[x][k], y=f[y][k];
        return f[x][0];
    }
    int jp(int x, int b)
    {
        for(auto k=0;k<=maxk;k++)
            if(b&(1<<k))x=f[x][k];
        return x;
    }
}db;
struct Easy_Tree
{
    int depth[maxn], dist[maxn], tid[maxn], rtid[maxn], tim, size[maxn], rev[maxn];
    void dfs(int pos, int pre, Graph& G)
    {
        tid[pos]=++tim;
        rev[tid[pos]]=pos;
        size[pos]=1;
        forp(pos,G)if(G.to[p]!=pre)
        {
            depth[G.to[p]]=depth[pos]+1;
            dist[G.to[p]]=dist[pos]+G.w[p];
            dfs(G.to[p],pos,G);
            size[pos]+=size[G.to[p]];
        }
        rtid[pos]=tim;
    }
    void run(Graph& G, int root)
    {
        tim=0;
        depth[root]=1;
        dfs(1,0,G);
    }
}et;
int root[maxn], a[maxn];
void NewApple(int u, int x)
{
    int t=u;
    while(t)
    {
        int lca = db.q(u,t), L = et.dist[u] + et.dist[t] - 2*et.dist[lca];
        root[t]=segtree.insert(root[t],x,L,1,10000);
        t = CD.fa[t];
    }
}
int main()
{
	int n=read(), m=read(), u, v, w, x, y, i;
    rep(i,1,n)a[i]=read();
	rep(i,1,n-1)
	{
		u=read(), v=read(), w=read();
		G.adde(u,v,w), G.adde(v,u,w);
	}
	et.run(G,1);
	db.run(G,1);
	CD.run(G);
    rep(i,1,n)NewApple(i,a[i]);
	while(m--)
	{
		int type=read();
		if(type==1)
		{
			u=read();
			x=read();
			NewApple(u,x);
		}
		else
		{
			u=read();
			x=read();
			y=read();
			int t=u, ans=iinf;
			while(t)
			{
				int lca = db.q(u,t), L = et.dist[u] + et.dist[t] - 2*et.dist[lca];
				ans = min( ans, L + segtree.qmin(root[t],x,y,1,10000) );
				t = CD.fa[t];
			}
			printf("%d\n",ans==iinf?-1:ans*2);
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值