zoukankan      html  css  js  c++  java
  • P4556 [Vani有约会]雨天的尾巴 /【模板】线段树合并

    P4556 [Vani有约会]雨天的尾巴 /【模板】线段树合并

    题目

    题目背景

    题目背景

    深绘里一直很讨厌雨天。

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

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

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

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

    题目描述

    首先村落里的一共有 (n) 座房屋,并形成一个树状结构。然后救济粮分 (m) 次发放,每次选择两个房屋 ((x,~y)),然后对于 (x)(y) 的路径上(含 (x)(y))每座房子里发放一袋 (z) 类型的救济粮。

    然后深绘里想知道,当所有的救济粮发放完毕后,每座房子里存放的最多的是哪种救济粮。

    输入格式

    输入的第一行是两个用空格隔开的正整数,分别代表房屋的个数 (n) 和救济粮发放的次数 (m)

    (2) 到 第 (n) 行,每行有两个用空格隔开的整数 (a,~b),代表存在一条连接房屋 (a)(b) 的边。

    ((n + 1)) 到第 ((n + m)) 行,每行有三个用空格隔开的整数 (x,~y,~z),代表一次救济粮的发放是从 (x)(y) 路径上的每栋房子发放了一袋 (z) 类型的救济粮。

    输出格式

    输出 (n) 行,每行一个整数,第 (i) 行的整数代表 (i) 号房屋存放最多的救济粮的种类,如果有多种救济粮都是存放最多的,输出种类编号最小的一种。

    如果某座房屋没有救济粮,则输出 (0)

    输入输出样例

    输入 #1

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

    输出 #1

    2
    3
    3
    0
    2
    

    说明/提示

    • 对于 (20\%) 的数据,保证 (n, m leq 100)
    • 对于 (50\%) 的数据,保证 (n, m leq 2 imes 10^3)
    • 对于 (100\%) 测试数据,保证 (1 leq n, m leq 10^5)(1 leq a,b,x,y leq n)(1 leq z leq 10^5)

    思路

    若用(a_{i,j})表示结点(i)上救济粮(j)的数量,则结点(i)的答案就是(k),满足(a_{i,k})最大,(k)最小,即最值所在位置.

    我们考虑建一个(a)的(树上)差分数组(b):

    [a_{u,i}=b_{u,i}+sum_{vin son(i)}b_{v,i} ]

    但是一个(i)一个(i)求,还要维护最值,肯定是爆时空复杂度的.

    在看看问题,一次发救济粮,就改了几个数字:(b_{x,z} , b_{y,z} , b_{lca(x,y),z} , b_{fa(lca(x,y)),z})(具体参考树上差分),也就是说(b)数组很多都是空的.所以,我们用 动态开点 + 线段树合并 来维护(a,b),具体看代码.

    代码

    #include <iostream>
    #include <cstdio>
    #include <cmath>
    using namespace std;
    int read() {
    	int re = 0;
    	char c = getchar();
    	bool negt = false;
    	while(c < '0' || c > '9')
    		negt |= (c == '-') , c = getchar();
    	while(c >= '0' && c <= '9')
    		re = (re << 1) + (re << 3) + c - '0' , c = getchar();
    	return negt ? -re : re;
    }
    
    const int N = 100010;
    const int maxZ = 100000;
    const int M = 100010;
    const int M_logZ = M * 20;
    
    struct EDGE {
    	int to , nxt;
    } ed[N * 2];
    int head[N];
    void addedge(int u , int v) {
    	static int cnt;
    	++cnt;
    	ed[cnt].to = v , ed[cnt].nxt = head[u] , head[u] = cnt;
    }
    
    int n , m , root;
    
    int father[N];
    namespace LCA {//RMQ-LCA,LCA就对了
    	int cnt = 0;
    	int dep[N * 2] , id[N * 2] , first[N];
    	int st[N * 4][25];
    	void dfs(int u , int nowdep) {
    		++cnt;
    		id[cnt] = u , dep[cnt] = nowdep , first[u] = cnt;
    		for(int i = head[u] ; i ; i = ed[i].nxt) {
    			int v = ed[i].to;
    			if(v == father[u])
    				continue;
    			father[v] = u;
    			dfs(v , nowdep + 1);
    			++cnt;
    			id[cnt] = u , dep[cnt] = nowdep;
    		}
    	}
    	void init(int root) {
    		dfs(root , 1);
    
    		for(int i = 1 ; i <= cnt ; i++)
    			st[i][0] = i;
    		int k = log(n) / log(2) + 1;
    		dep[0] = 0x3fffffff;
    		for(int j = 1 ; j <= k ; j++)
    			for(int i = 1 ; i <= cnt ; i++)
    				st[i][j] = dep[st[i][j - 1]] < dep[st[i + (1 << j - 1)][j - 1] ] ? st[i][j - 1] : st[i + (1 << j - 1)][j - 1];
    
    	}
    	int lca(int u , int v) {
    		u = first[u] , v = first[v];
    		if(u > v) {
    			int tmp;
    			tmp = u , u = v , v = tmp;
    		}
    		int k = log(v - u + 1) / log(2);
    		return id[
    		           dep[st[u][k]] < dep[st[v - (1 << k) + 1][k]] ? st[u][k] : st[v - (1 << k) + 1][k]
    		       ];
    	}
    }
    inline int dep(int u) {
    	return LCA::dep[LCA::first[u]];
    }
    
    
    namespace SegT {//我的一个代码习惯:函数中的p表示当前递归到的线段树结点的编号
    	int root[N];
    	struct NodeClass {
    		int l , r , ls , rs , dat_num , dat_id;
    //当前区间[l,r] , ls/rs:LeftSon,RightSon左右子结点编号 , dat_num:区间最值(存放最多的救济粮的数量) ,dat_id:区间最值出现的位置(该救济粮的种类)
    	} node[4 * M_logZ];//其实我也不知道数组要开多大QAQ
    	inline int newnode (int l , int r) {//开点
    		static int cnt = 0;
    		++cnt;
    		node[cnt].l = l , node[cnt].r = r , node[cnt].dat_id = l;
    		return cnt;
    	}
    	inline void push_up(int p) {//递归子结点后,更新结点p的数据
    #define ls(p) node[p].ls
    #define rs(p) node[p].rs
    		int tmp;
    		if(ls(p) == 0)
    			tmp = rs(p);
    		else if(rs(p) == 0)
    			tmp = ls(p);
    		else if(node[ls(p)].dat_num >= node[rs(p)].dat_num)//注意等号
    			tmp = ls(p);
    		else
    			tmp = rs(p);
    		node[p].dat_id = node[tmp].dat_id , node[p].dat_num = node[tmp].dat_num;
    #undef ls
    #undef rs
    	}
    	void change(int p , int pos , int dat) {
    		if(node[p].l == node[p].r) {
    			node[p].dat_num += dat;
    			return;
    		}
    
    		int mid = (node[p].l + node[p].r) / 2;
    		if(pos <= mid)//这里比较压行,其实就是先检查左/右子树是否为空,若为空,先开一个结点,然后递归
    			change(node[p].ls = (node[p].ls == 0 ? newnode(node[p].l , mid) : node[p].ls) , pos , dat);
    		else
    			change(node[p].rs = (node[p].rs == 0 ? newnode(mid + 1 , node[p].r) : node[p].rs) , pos , dat);
    		push_up(p);
    	}
    	int merge(int p1 , int p2 , int l , int r) {//合并以p1,p2为根的线段树
    		if(p1 == 0 || p2 == 0)
    			return p1 + p2;
    		if(l == r) {
    			node[p1].dat_num += node[p2].dat_num;
    			return p1;
    		}
    		int mid = (l + r) / 2;
    		node[p1].ls = merge(node[p1].ls , node[p2].ls , l , mid);
    		node[p1].rs = merge(node[p1].rs , node[p2].rs , mid + 1 , r);
    		push_up(p1);
    		return p1;
    	}
    }
    
    
    int ans[N];
    void calc(int u) {
    	for(int i = head[u] ; i ; i = ed[i].nxt) {
    		int v = ed[i].to;
    		if(v == father[u])
    			continue;
    		calc(v);
    		SegT::root[u] = SegT::merge(SegT::root[u] , SegT::root[v] , 1 , maxZ);
    	}
    	ans[u] = SegT::node[SegT::root[u]].dat_num == 0 ? 0 : SegT::node[SegT::root[u]].dat_id;
    }
    int main() {
    	n = read() , m = read() , root = 1;
    	for(int i = 1 ; i < n ; i++) {
    		int u = read() , v = read();
    		addedge(u , v) , addedge(v , u);
    	}
    	LCA::init(root);
    
    	for(int i = 1 ; i <= n ; i++)
    		SegT::root[i] = SegT::newnode(1 , maxZ);
    	for(int i = 1 ; i <= m ; i++) {
    		int u = read() , v = read() , z = read();
    		int lca = LCA::lca(u , v);
    		SegT::change(SegT::root[u] , z , 1);
    		SegT::change(SegT::root[v] , z , 1);
    		SegT::change(SegT::root[lca] , z , -1);
    		SegT::change(SegT::root[father[lca]] , z , -1);
    	}
    	calc(root);
    	for(int i = 1 ; i <= n ; i++)
    		printf("%d
    " , ans[i]);
    	return 0;
    }
    
  • 相关阅读:
    libevent简介[翻译]3 前言
    libevent简介[翻译]2 关于异步IO的简单介绍
    gdb调试子进程
    openSUSE Firewalld
    libevent简介[翻译]1 关于这个文档
    构建libevent pdf 文档
    openSUSE编译libevent
    为openSUSE添加源
    .Net Core3.0 WebApi 项目框架搭建 五:仓储模式
    .Net Core3.0 WebApi 项目框架搭建 四:JWT权限验证
  • 原文地址:https://www.cnblogs.com/dream1024/p/15163526.html
Copyright © 2011-2022 走看看