• AcWing 353 雨天的尾巴


    写在前面

    居然没有树剖的题解……
    我来水一发

    题目描述

    深绘里一直很讨厌雨天。

    灼热的天气穿透了前半个夏天,后来一场大雨和随之而来的洪水,浇灭了一切。

    虽然深绘里家乡的小村落对洪水有着顽固的抵抗力,但也倒了几座老房子,几棵老树被连根拔起,以及田地里的粮食被弄得一片狼藉。

    无奈的深绘里和村民们只好等待救济粮来维生。

    不过救济粮的发放方式很特别。

    有 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。

    数据范围
    (1≤n,m≤100000),
    (1≤z≤10^9)

    样例

    输入样例

    5 3
    1 2
    3 1
    3 4
    5 3
    2 3 3
    1 5 2
    3 3 3
    

    输出样例

    2
    3
    3
    0
    2
    

    算法1

    线段树合并

    有大佬已经写了,我就懒得赘述

    
    #include <bits/stdc++.h>
    #define lson l,mid,tree[now].l
    #define rson mid + 1,r,tree[now].r
    
    using namespace std;
    
    const int maxn = 1e5 + 5;
    const int maxm = 6e6 + 5;//数组还是 开大点 
    
    int n,m,size,first[maxn],tot,cntz;
    int root[maxn],tmp[maxn],top[maxn],cnt[maxn],dep[maxn],father[maxn],ans[maxn];
    
    struct Query{int x,y,z;}ask[maxn];
    struct Edge{int v,nt;}edge[maxn << 1];
    struct SegMentTree{int l,r,val,id;}tree[maxm]; 
    //----------输入输出优化
    char *TT,*mo,but[(1 << 18) + 2]; 
    #define getchar() ((TT == mo && (mo = ((TT = but) + fread(but, 1, 1 << 18, stdin)),TT == mo)) ? -1 : *TT++) 
    template<class T>inline void read(T &x){
    	x = 0;bool flag = 0;char ch = getchar();
    	while(!isdigit(ch)) flag |= ch == '-',ch = getchar();
    	while(isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48),ch = getchar();
    	if(flag) x = -x;
    }
    
    template<class T>void putch(const T x){if(x > 9) putch(x / 10);putchar(x % 10 | 48);}
    template<class T>void put(const T x){if(x < 0) putchar('-'),putch(-x);else putch(x);}
    //---------读入数据 
    void eadd(int u,int v){edge[++size].v = v;edge[size].nt = first[u];first[u] = size;}
    
    void readdata(){
    	read(n);read(m);
    	for(int i = 1;i < n; ++ i){int u,v;read(u);read(v);eadd(u,v);eadd(v,u);}
    	for(int i = 1;i <= m; ++ i){read(ask[i].x);read(ask[i].y);read(ask[i].z);tmp[i] = ask[i].z;}
    }
    //----------LCA
    void dfs(int u,int f,int d){
    	top[u] = u,dep[u] = d,father[u] = f;cnt[u] = 1;int son = 0,mcnt = 0;
    	for(int i = first[u];i;i = edge[i].nt){
    		int v = edge[i].v;if(v == f) continue;
    		dfs(v,u,d + 1);cnt[u] += cnt[v];
    		if(cnt[v]>mcnt) mcnt = cnt[v],son = v;
    	}
    	if(son) top[son] = u;
    }
    
    int find(int x){return top[x] == x ? x : top[x] = find(top[x]);};
    
    int LCA(int x,int y){
    	if(find(x) == find(y)) return dep[x] < dep[y] ? x : y;
    	else return dep[top[x]] < dep[top[y]] ? LCA(x,father[top[y]]) : LCA(father[top[x]],y);
    }
    //----------线段树的修改 & 合并 
    void pushup(int k){
    	int ls = tree[k].l,rs = tree[k].r;
    	if(tree[ls].val >= tree[rs].val) tree[k].val = tree[ls].val,tree[k].id = tree[ls].id;
    	else tree[k].val = tree[rs].val,tree[k].id = tree[rs].id;
    }
    
    void modify(int l,int r,int &now,int pos,int val){
    	if(!now) now = ++tot;
    	if(l == r && l == pos){tree[now].val += val;tree[now].id = l;return;}
    	int mid = (l + r) >> 1;
    	if(pos <= mid) modify(lson,pos,val);
    	else modify(rson,pos,val);
    	pushup(now);//记得pushup 
    }
    
    void merge(int &x,int &y,int l,int r){
    	if(!x) return;if(!y) {y = x;return;}
    	if(l == r){tree[y].val += tree[x].val;return;}
    	int mid = (l + r) >> 1;
    	merge(tree[x].l,tree[y].l,l,mid);merge(tree[x].r,tree[y].r,mid + 1,r);
    	pushup(y);
    } 
    
    void merge_dfs(int u,int f){
    	for(int i = first[u];i;i = edge[i].nt){
    		int v = edge[i].v;if(v == f) continue;//v = edge[i].v
    		merge_dfs(v,u);merge(root[v],root[u],1,cntz);
    	}
    	if(tree[root[u]].val == 0) ans[u] = 0;//当没有救济粮时输出0 
    	else ans[u] = tmp[tree[root[u]].id];
    }
    //---------主模块 
    void work(){
    	//----------初始化 
    	dfs(1,0,1);
    	//----------离散化 + 修改 
    	sort(tmp + 1,tmp + m + 1);
    	cntz = unique(tmp + 1,tmp + m + 1) - (tmp + 1);
    	for(int i = 1;i <= m; ++ i){
    		ask[i].z = lower_bound(tmp + 1,tmp + cntz+ 1,ask[i].z) - tmp;		
    		int lca = LCA(ask[i].x,ask[i].y);
    		modify(1,cntz,root[ask[i].x],ask[i].z,1);
    		modify(1,cntz,root[ask[i].y],ask[i].z,1);
    		modify(1,cntz,root[lca],ask[i].z,-1);
    		if(father[lca])modify(1,cntz,root[father[lca]],ask[i].z,-1);
    	} 
    	//----------合并
    	merge_dfs(1,0);
    	for(int i = 1;i <= n; ++ i){
    		put(ans[i]);putchar('
    ');
    	}
    }
    
    int main(){
    	readdata();
    	work();
    	return 0;
    }
    

    算法2

    树链剖分 (O(n(log n)^2))

    虽然看起来要多一个log,但是由于常数小,跑起来比线段树合并还要快。

    我们先回忆一下树剖做树上路径修改时的思想:

    把路径拆分成若干区间,在线段树上修改

    这道题也可以这么做

    我们可以把每一次修改拆成若干区间来做。由于我们需要记录救济粮的种类与数量,那么就需要离散化,建一棵权值线段树维护最值及对应的编号。

    将一条路经拆分后,由于这道题的空间卡的有些紧,不可能每个点建一棵线段树,所以我们考虑优化空间。

    除了动态开点,我们应该还可以想到:树剖时,每个点对应的编号是一定的,而询问拆分过后,每一个区间的编号都是连续的,而我们要求的最终答案是在所有操作之后,可以离线求出每个点的答案。

    这个编号在一般的树剖里是对应的线段树的编号,在这里由于建的是权值线段树,这些编号的意义不如就理解为一个时间序编号。

    对于拆分后的每个连续区间的修改,若是按左端点排序,那么,一个点后面的区间的修改,是不会影响到已经修改完的点的,这样我们便可以差分处理,把编号理解为时间,直接在一颗线段树上求解。

    对于每个区间修改([a_i,b_i]),可以进一步拆分成在时间(a_i)处对应的救济粮+1,在时间(b_i+1)处对应的救济粮-1,便于优化与求解,我们可以把每个时间的询问用类似链表的方式存储,每个时间求解完毕后,就可以算出对应的节点的答案。

    这样的差分修改,前缀和求解只需开一颗线段树即可。而询问拆分大约拆为nlogn个。

    整理一下思路:

    1. 开始时,将路径的修改拆分成一个个编号连续的区间([a_i,b_i]),对于每个区间有用差分思想拆为(a_i)处val的个数+1,(b_i)处val的个数-1,用类似链表的方式存下每个编号(时间)所对应的的各个修改。

    2. 然后,从1开始遍历所有编号的修改,在一颗权值线段树上执行,每个编号的修改遍历完之后,将编号对应的节点的答案记录下来(因为差分操作,这里相当于求前缀和)

    C++ 代码

    
    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int maxn = 1e5 + 5;
    
    int n,m,size,first[maxn],tot,head[maxn],num,rev[maxn],son[maxn],cntz;//head存询问的“头边” 
    int top[maxn],seg[maxn],father[maxn],dep[maxn],cnt[maxn],ans[maxn],tmp[maxn];
    
    struct Edge{int v,nt;}edge[maxn << 1];
    struct Query{int x,y,z;}a[maxn];//拆分前的询问
    struct Split_Query{int val,nt;}ask[maxn * 20];//拆分后的询问
    struct SegmentTree{int val,id;}tree[maxn << 2];
    //----------输入输出优化 
    //char *TT,*mo,but[(1 << 18) + 2]; 
    //#define getchar() ((TT == mo && (mo = ((TT = but) + fread(but, 1, 1 << 18, stdin)),TT == mo)) ? -1 : *TT++) 
    template<class T>inline void read(T &x){
    	x = 0;
    	bool flag = 0;
    	char ch = getchar();
    	while(!isdigit(ch)) flag |= ch == '-',ch = getchar();
    	while(isdigit(ch)) x = (x << 1) + (x << 3) + (ch ^ 48),ch = getchar();
    	if(flag) x = -x;
    }
    template<class T>void putch(const T x){if(x > 9) putch(x / 10);putchar(x % 10 | 48);}
    template<class T>void put(const T x){if(x < 0) putchar('-'),putch(-x);else putch(x);}
    //----------读入数据 & 初始化树剖 
    void eadd(int u,int v){edge[++size].v = v;edge[size].nt = first[u];first[u] = size;} 
    
    void dfs1(int u,int f){
    	father[u] = f;cnt[u] = 1;int mcnt = 0;
    	for(int i = first[u];i;i = edge[i].nt){
    		int v = edge[i].v;if(v == f) continue;
    		dep[v] = dep[u] + 1;dfs1(v,u);cnt[u] += cnt[v];//注意累加 
    		if(cnt[v] > mcnt) mcnt = cnt[v],son[u] = v;
    	}
    }
    
    void dfs2(int u,int f,int tp){
    	seg[u] = ++tot;top[u] = tp;rev[seg[u]] = u;
    	if(son[u]) dfs2(son[u],u,tp);
    	for(int i = first[u];i;i = edge[i].nt){
    		int v = edge[i].v;if(v == f || v == son[u]) continue;
    		dfs2(v,u,v);
    	}
    }
    
    void readdata(){
    	read(n);read(m);
    	for(int i = 1;i < n; ++ i){int u,v;read(u);read(v);eadd(u,v);eadd(v,u);}
    	dfs1(1,0);dfs2(1,0,1);
    	//离散化 
    	for(int i = 1;i <= m; ++ i) read(a[i].x),read(a[i].y),read(a[i].z),tmp[i] = a[i].z;
    	sort(tmp + 1,tmp + m + 1);
    	cntz = unique(tmp + 1,tmp + m + 1) - (tmp + 1); 
    }
    //----------拆分修改 
    void add_query(int id,int val){ask[++num].val = val;ask[num].nt = head[id];head[id] = num;}
    
    void Split(int x,int y,int z){
    	while(top[x] != top[y]){
    		if(dep[top[x]] < dep[top[y]]) swap(x,y);
    		add_query(seg[top[x]],z);add_query(seg[x] + 1,-z);//top[u]的编号小于u
    		x = father[top[x]]; 
    	}
    	if(dep[x] < dep[y]) swap(x,y);
    	add_query(seg[y],z);add_query(seg[x] + 1,-z);
    }
    //----------线段树模块 
    void pushup(int k){
    	int ls = k << 1,rs = k << 1 | 1;
    	if(tree[ls].val >= tree[rs].val) tree[k].val = tree[ls].val,tree[k].id = tree[ls].id;//>=
    	else tree[k].val = tree[rs].val,tree[k].id = tree[rs].id;
    }
    
    void modify(int l,int r,int k,int pos,int val){
    	if(l == r){tree[k].val += val;tree[k].id = l;return;}
    	int mid = (l + r) >> 1;
    	if(pos <= mid) modify(l,mid,k<<1,pos,val);
    	else modify(mid + 1,r,k<<1|1,pos,val);
    	pushup(k);
    }
    //----------主模块 
    void work(){
    //----------拆分修改 
    	for(int i = 1;i <= m; ++ i) {
    		a[i].z = lower_bound(tmp + 1,tmp + 1 + cntz,a[i].z) - tmp; 
    		Split(a[i].x,a[i].y,a[i].z);
    	}//luogu上似乎不用离散化
    //----------修改 
    	for(int i = 1;i <= n; ++ i){//这里是seg值,也就是时间 
    		for(int j = head[i];j;j = ask[j].nt){
    			if(ask[j].val > 0) modify(1,cntz,1,ask[j].val,1);
    			else modify(1,cntz,1,-ask[j].val,-1);
    		}
    		ans[rev[i]] = tree[1].val ? tmp[tree[1].id] : 0;
    	}
    	for(int i = 1;i <= n; ++ i){
    		put(ans[i]);putchar('
    ');
    	}
    }
    
    int main(){
    //	freopen("testdata.in","r",stdin);
    //	freopen("testdata.out","w",stdout);
    	readdata();
    	work();
    	return 0;
    }
    
  • 相关阅读:
    词法分析程序
    关于编译原理
    超神c语言文法
    linux基本命令
    用bat来注册ocx
    当web配置文件 appSettings配置的东西越来越多时,可以拆开了。
    MVC的URL路由规则
    ASP.NET MVC 中如何实现基于角色的权限控制
    查cc攻击
    关于session介绍
  • 原文地址:https://www.cnblogs.com/Mandy-H-Y/p/11779289.html
Copyright © 2020-2023  润新知