zoukankan      html  css  js  c++  java
  • 雨天的尾巴「线段树合并+树上差分」

    雨天的尾巴「线段树合并+树上差分」

    题目描述(简化版)

    (N) 个点,形成一个树状结构。有 (M) 次发放,每次选择两个点 (x,y) 对于 (x)(y) 的路径上(含 (x,y))每个点发一袋 (Z) 类型的物品。完成所有发放后,每个点存放最多的是哪种物品。

    输入格式

    第一行数字 (N,M)
    接下来 (N-1) 行,每行两个数字 (a,b),表示 (a)(b) 间有一条边
    再接下来 (M) 行,每行三个数字 (x,y,z).如题

    输出格式

    输出有 (N)
    (i) 行的数字表示第 (i) 个点存放最多的物品是哪一种,如果有多种物品的数量一样,输出编号最小的。如果某个点没有物品则输出 (0)

    思路分析

    板子题

    • 首先发现这是在树上进行区间修改,所以可以用树上差分来处理
    • 每一个节点都要记录不同种类的物品的个数,因此每一个节点都开一个权值线段树,以物品种类编号为下标
    • 因为使用了树上差分,所以每一个节点的线段树都要与其子树内的所有节点的线段树合并,类似前缀和思想,这样就可以得到该节点最终的线段树,就可以更新答案了

    详见代码

    (Code)

    #include<cstdio>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #define N 100005
    #define M 6000005
    #define R register
    using namespace std;
    inline int read(){
    	int x = 0,f = 1;
    	char ch = getchar();
    	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
    	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    	return x*f;
    }
    int n,m,ed,head[N],siz[N],dep[N],f[N],son[N],top[N];
    int tr[M],mx[M],ls[M],rs[M];//tr记录的是线段树中每个物品的种类数,mx记录的是种类数最多的物品
    int ans[N],cnt,x[N],y[N],z[N],rt[N]; //rt表示每个结点的线段树的根节点
    struct edge{
    	int to,next;
    }e[N<<1];
    int len;
    void addedge(int u,int v){
    	e[++len].to = v;
    	e[len].next = head[u];
    	head[u] = len;
    }
    void dfs1(int u,int fa){ //树剖求LCA
    	siz[u] =  1;
    	dep[u] = dep[fa]+1;
    	f[u] = fa;
    	for(R int i = head[u];i;i = e[i].next){
    		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;
    	if(son[u])dfs2(son[u],tp);
    	for(R int i = head[u];i;i = e[i].next){
    		int v = e[i].to;
    		if(v!=son[u]&&v!=f[u])dfs2(v,v);
    	}
    }
    int LCA(int x,int y){
    	while(top[x]!=top[y]){
    		if(dep[top[x]]<dep[top[y]])swap(x,y);
    		x = f[top[x]];
    	}
    	return dep[x] < dep[y] ? x : y;
    }//————————————手动分割线——————————————
    
    //以下为线段树操作
    void pushup(int rt){
        if(tr[ls[rt]]>=tr[rs[rt]])tr[rt] = tr[ls[rt]],mx[rt] = mx[ls[rt]];
        else tr[rt] = tr[rs[rt]],mx[rt] = mx[rs[rt]];
    }
    int modify(int rt,int l,int r,int pos,int val){
    	if(!rt) rt = ++cnt; //动态开点
    	if(l==r){
    		tr[rt] += val;
    		mx[rt] = l;
    		return rt;
    	}
    	int mid = (l+r)>>1;
    	if(pos<=mid)ls[rt] = modify(ls[rt],l,mid,pos,val);
    	else rs[rt] = modify(rs[rt],mid+1,r,pos,val);
    	pushup(rt);
    	return rt;
    }
    int merge(int a,int b,int l,int r){ //线段树合并
    	if(!a)return b;
    	if(!b)return a;
    	if(l==r){
    		tr[a] += tr[b];
    		mx[a] = l;
    		return a;
    	}
    	int mid = (l+r)>>1;
    	ls[a] = merge(ls[a],ls[b],l,mid),rs[a] = merge(rs[a],rs[b],mid+1,r);
    	pushup(a);
    	return a;
    }
    void get_ans(int u){//dfs统计差分答案
    	for(R int i = head[u];i;i = e[i].next){
    		int v = e[i].to;
    		if(dep[v]>dep[u]){
    			get_ans(v);
    			rt[u] = merge(rt[u],rt[v],1,ed);
    		}
    	}
    	if(tr[rt[u]])ans[u] = mx[rt[u]];
    }
    int main(){
    	n = read(),m = read();
    	for(R int i = 1;i < n;i++){
    		int a = read(),b = read();
    		addedge(a,b),addedge(b,a);
    	}
    	dfs1(1,0),dfs2(1,1);//注意是dfs1(1,0)而不是dfs1(1,1),因为这个坑花了多长时间就不提了:-)
    	for(R int i = 1;i <= m;i++){
    		x[i] = read(),y[i] = read(),z[i] = read();
    		ed = max(ed,z[i]);
    	}
    	for(R int i=1;i<=m;i++){//差分处理
    		int lca=LCA(x[i],y[i]);
    		rt[x[i]]=modify(rt[x[i]],1,ed,z[i],1),rt[y[i]]=modify(rt[y[i]],1,ed,z[i],1);
    		rt[lca]=modify(rt[lca],1,ed,z[i],-1);
    		if(f[lca]) rt[f[lca]]=modify(rt[f[lca]],1,ed,z[i],-1);
            }
    	get_ans(1);
    	for(R int i = 1;i <= n;i++)printf("%d
    ",ans[i]);
    	return 0;
    }
    
  • 相关阅读:
    Linux 安装nginx
    Linux服务器svn与项目同步
    Linux服务器安装svn
    Thinkphp5模板继承
    Thinkphp5 Route用法
    一键切换hosts文件
    lnmp手动新建虚拟机
    wamp 配置虚拟主机
    百度编辑器
    百度编辑器:上传图片二
  • 原文地址:https://www.cnblogs.com/hhhhalo/p/13637615.html
Copyright © 2011-2022 走看看