zoukankan      html  css  js  c++  java
  • 题解 【P4315】 月下“毛景树”

    看原题戳这儿

    如题,肯定是树链剖分的题。

    零、写在前面

    建议先A掉这道模板题(不会的先看这个

    前置知识:链式前向星,树,dfs序,LCA,树形dp,线段树,树链剖分(······)

    一定要先完全学懂,否则不保证这篇题解能完全看懂!!!

    一、审题

    先简化题目:

    已知一棵包含 (N(0 le N le 100000)) 个结点的树(连通且无环),每条边上包含一个数值,需要支持以下操作:

    • (Change k w):将第(k)条边上数值改变为(w)

    • (Covr u v w):将节点(u)与节点(v)之间的最短路径上数值都改变为(w)个。

    • (Add u v w):将节点(u)与节点(v)之间的最短路径上数值都增加(w)

    • (Max u v):询问节点(u)与节点(v)之间的最短路径上的数值之和最大是多少。

    瞄一眼数据,(N)最大是({10}^5),肯定要用(O(nlogn))的算法,树剖和线段树刚好合适。

    二、难点

    这道题虽然说已经确定下来要用树链剖分来做了,但是树链剖分和线段树不都是求点吗?怎么可以用来求边呢?这就是这道题目的毒瘤之处。

    那应该怎么做呢?

    答:边权换点权!!!

    实现

    如下图所示,把每边权值赋给其下端的点(根节点为0)

    然后就是模板的问题了

    但有几个细节,本题又加又覆,需要两个懒标记,记得处理他们的关系

    还有一件事,当查询时转化为一条重链时,如图:

    此时我们要求(u,v)两点间的权值最大值,但是(u)节点存的是红边的值,这不是我们要找的答案

    (sun_u)存的是蓝边,这正是我们的目标

    所以最后要查询的区间是(son_u)(v)之间的最大值

    三、预处理

    dfs1()

    这个dfs要处理这四件事情:

    1. 标记每个点的深度dep[ ]

    2. 标记每个点的父亲fa[ ]

    3. 标记每个非叶子节点的子树大小(含它自己)

    4. 标记每个非叶子节点的重儿子编号son[ ]

    void dfs1(int u,int fa)//u表示当前节点,fa表示当前节点的父亲
    {
    	f[u]=fa;//处理父亲
    	siz[u]=1;//先标记
    	son[u]=0;//最大的儿子
    	dep[u]=dep[fa]+1;//处理深度
    	for(int i=head[u];i!=0;i=e[i].nxt)
    	{
    		int v=e[i].to;
    		if(v==fa) continue;//判重
    		dfs1(v,u);siz[u]+=siz[v];
    		if(siz[v]>siz[son[u]]) son[u]=v;//判断是否是重儿子
    	}
    }
    

    dfs2()

    这个dfs要处理这四件事情:

    1. 标记每个点的新编号

    2. 赋值每个点的初始值到新编号上

    3. 处理每个点所在链的顶端

    4. 处理每条链

    顺序:先处理重儿子再处理轻儿子

    (OneProblem)

    为什么要先处理重儿子?

    答:这样在后面我们所要处理的所有区间中点的编号(新编号) 均为连续的 ,这样方便我们建线段树

    建树

    (其实就和线段树一样)

    前面说到要用线段树,那么按题意建树就可以了。

    不过需要注意的是,建树这一步要放在处理问题之前,不然(······)

    四、树剖

    1、处理任意两点间路径时: 设所在链顶端的深度更深的那个点为x点

    方法:

    • ans加上x点到x所在链顶端 这一段区间的点权和

    • 把x跳到x所在链顶端的那个点的上面一个点

    不停执行这两个步骤,直到两个点处于一条链上,这时再加上此时两个点的区间和即可

    这时我们注意到,我们所要处理的所有区间均为连续编号(新编号) ,于是想到线段树,用线段树处理连续编号区间和(为什么代码这么长?就因为这儿)

    每次查询时间复杂度为(O(log^2n)),不错。

    void Tupdate(int a,int b,int c,int flag)//树剖区间修改
    {
    	while(top[a]!=top[b])
    	{
    		if(dep[top[a]]<dep[top[b]]) swap(a,b);
    		if(flag==1) update1(1,1,n,tid[top[a]],tid[a],c);
    		else update2(1,1,n,tid[top[a]],tid[a],c);
    		a=f[top[a]];
    	}
    	if(a==b) return;
    	if(dep[a]>dep[b]) swap(a,b);
    	if(flag==1) update1(1,1,n,tid[son[a]],tid[b],c);
    	else update2(1,1,n,tid[son[a]],tid[b],c);
    }
    

    2、处理一点及其子树的点权和:

    想到要记录每个非叶子节点的子树大小(含它自己),并且每个子树的新编号都是连续的,然后直接线段树区间查询就行啦!!!时间复杂度为$ O(logn) $,很好

    inline int qSon(int x)
    {
        res=0;
        query(1,1,a,id[x],id[x]+siz[x]-1);//子树区间右端点id[x]+siz[x]-1 
        return res;
    }
    

    当然,区间修改是和区间查询一样的

    int Tquery(int a,int b)//树剖区间查询
    {
    	int ans=-1e9;
    	while(top[a]!=top[b])
    	{
    		if(dep[top[a]]<dep[top[b]])
    		swap(a,b);
    		ans=max(ans,query(1,1,n,tid[top[a]],tid[a]));
    		a=f[top[a]];
    	}
    	if(a==b) return ans;
    	if(dep[a]>dep[b]) swap(a,b);
    	ans=max(ans,query(1,1,n,tid[son[a]],tid[b]));
    	return ans;
    }
    

    五、代码

    #include<bits/stdc++.h>
    using namespace std;
    using namespace std;
    struct node{
    	int to,nxt;
    }e[200001];
    int pos[100001],tid[100001],top[100001],dep[100001],son[100001],siz[100001],f[100001],head[100001];
    int d[100001][5],sum[500001],col[200001],lzy[500001];
    int n,m,cnt,cnt2;
    void add(int u,int v)//加边函数,记得加双向
    {
    	e[++cnt].to=v;
    	e[cnt].nxt=head[u];
    	head[u]=cnt;
    }
    void dfs1(int u,int fa)//两个dfs初始化
    {
    	f[u]=fa;
    	siz[u]=1;
    	son[u]=0;
    	dep[u]=dep[fa]+1;
    	for(int i=head[u];i!=0;i=e[i].nxt)
    	{
    		int v=e[i].to;
    		if(v==fa)
    		continue;
    		dfs1(v,u);siz[u]+=siz[v];
    		if(siz[v]>siz[son[u]]) son[u]=v;
    	}
    }
    void dfs2(int u,int tp)
    {
    	top[u]=tp;
    	tid[u]=++cnt2;
    	pos[cnt2]=u;
    	if(son[u]) dfs2(son[u],tp);
    	for(int i=head[u];i!=0;i=e[i].nxt)
    	{
    		int v=e[i].to;
    		if(v==f[u]||v==son[u])
    		continue;
    		dfs2(v,v);
    	}
    }
    void pushup(int id)
    {
    	sum[id]=max(sum[id<<1],sum[id<<1|1]);
    }
    void pushdown(int id)
    {
    	if(lzy[id]!=-1)
    	{
    		col[id<<1]=col[id<<1|1]=0;
    		sum[id<<1]=sum[id<<1|1]=lzy[id];
    		lzy[id<<1]=lzy[id<<1|1]=lzy[id];
    		lzy[id]=-1;
    	}
    	if(col[id])
    	{
    		col[id<<1]+=col[id];
    		col[id<<1|1]+=col[id];
    		sum[id<<1]+=col[id];
    		sum[id<<1|1]+=col[id];
    		col[id]=0;
    	}
    }
    void build(int id,int l,int r)
    {
    	lzy[id]=-1;
    	if(l==r)
    	return;
    	int mid=(l+r)>>1;
    	build(id<<1,l,mid);build(id<<1|1,mid+1,r);
    	pushup(id);
    }
    void update1(int id,int l,int r,int x,int y,int z)//区间加数
    {
    	if(x<=l&&r<=y)
    	{
    		sum[id]+=z;
    		col[id]+=z;
    		return;
    	}
    	int mid=(l+r)>>1;
    	pushdown(id);
    	if(x<=mid) update1(id<<1,l,mid,x,y,z);
    	if(y>mid) update1(id<<1|1,mid+1,r,x,y,z);
    	pushup(id);
    }
    void update2(int id,int l,int r,int x,int y,int z)//区间覆盖
    {
    	if(x<=l&&r<=y)
    	{
    		sum[id]=z;
    		lzy[id]=z;
    		col[id]=0;
    		return;
    	}
    	int mid=(l+r)>>1;
    	pushdown(id);
    	if(x<=mid) update2(id<<1,l,mid,x,y,z);
    	if(y>mid) update2(id<<1|1,mid+1,r,x,y,z);
    	pushup(id);
    }
    int query(int id,int l,int r,int x,int y)//区间查询
    {
    	if(x<=l&&r<=y)
    	return sum[id];
    	int mid=(l+r)>>1,ans=-1e9;
    	pushdown(id);
    	if(x<=mid) ans=max(ans,query(id<<1,l,mid,x,y));
    	if(y>mid) ans=max(ans,query(id<<1|1,mid+1,r,x,y));
    	return ans;
    }
    void Tupdate(int a,int b,int c,int flag)//树剖区间修改
    {
    	while(top[a]!=top[b])
    	{
    		if(dep[top[a]]<dep[top[b]]) swap(a,b);
    		if(flag==1) update1(1,1,n,tid[top[a]],tid[a],c);
    		else update2(1,1,n,tid[top[a]],tid[a],c);
    		a=f[top[a]];
    	}
    	if(a==b) return;
    	if(dep[a]>dep[b]) swap(a,b);
    	if(flag==1) update1(1,1,n,tid[son[a]],tid[b],c);
    	else update2(1,1,n,tid[son[a]],tid[b],c);
    }
    int Tquery(int a,int b)//树剖区间查询
    {
    	int ans=-1e9;
    	while(top[a]!=top[b])
    	{
    		if(dep[top[a]]<dep[top[b]])
    		swap(a,b);
    		ans=max(ans,query(1,1,n,tid[top[a]],tid[a]));
    		a=f[top[a]];
    	}
    	if(a==b) return ans;
    	if(dep[a]>dep[b]) swap(a,b);
    	ans=max(ans,query(1,1,n,tid[son[a]],tid[b]));
    	return ans;
    }
    int main()//注:在这里我用了数组来存边,就不用乘以二了
    {
    	scanf("%d",&n);
    	for(int i=1;i<n;i++)
    	{
    		scanf("%d%d%d",&d[i][0],&d[i][1],&d[i][2]);
    		add(d[i][0],d[i][1]);
    		add(d[i][1],d[i][0]);
    	}
    	dfs1(1,1);
    	dfs2(1,1);
    	build(1,1,n);
    	for(int i=1;i<n;i++)
    	{
    		if(dep[d[i][0]]>dep[d[i][1]]) swap(d[i][0],d[i][1]);
    		update1(1,1,n,tid[d[i][1]],tid[d[i][1]],d[i][2]);
    	}
    	while(1)
    	{
    		int a,b,c;
    		char s[1001];
    		scanf("%s",&s);
    		if(s[0]=='S') return 0;
    		else if(s[0]=='A')
    		{
    			scanf("%d%d%d",&a,&b,&c);
    			Tupdate(a,b,c,1);
    		}
    		else if(s[1]=='o')
    		{
    			scanf("%d%d%d",&a,&b,&c);
    			Tupdate(a,b,c,2);
    		}
    		else if(s[1]=='h')
    		{
    			scanf("%d%d",&a,&b);
    			update2(1,1,n,tid[d[a][1]],tid[d[a][1]],b);
    		}
    		else if(s[0]=='M')
    		{
    			scanf("%d%d",&a,&b);
    			printf("%d
    ",Tquery(a,b));
    		}
    	}
    	return 0;
    }
    

    完美撒花!!!

  • 相关阅读:
    解决IE9下JQuery的ajax失效的问题
    npm更新到最新版本的方法
    animate.css配合wow.min.js实现各种页面滚动效果
    Bootstrap导航点击菜单跳转与点击缩放菜单折叠按钮缓冲效果插件jquery.singlePageNav.min.js
    对json对象进行截取并按照某关键字进行排序
    巧用 position:absolute
    EasyUI Datagrid 分页
    Cssreset
    杂记
    for循环遍历json(附习题及答案)
  • 原文地址:https://www.cnblogs.com/zhnzh/p/13393515.html
Copyright © 2011-2022 走看看