P4556 [Vani有约会]雨天的尾巴(线段树合并)

传送门

一道线段树合并
首先不难看出树上差分
我们把每一次修改拆成四个,在\(u,v\)分别放上一个,在\(lca\)和\(fa[lca]\)各减去一个,那么只要统计一下子树里的总数即可
然而问题就在于怎么统计。直接暴力肯定是要咕咕的,那么线段树合并就派上用场了
总之就是每个点开一个动态开点线段树,然后一遍dfs,让它的所有儿子的线段树合并到它这里
我按以前的写法不知为什么写挂了……然后换了种写法还是挂……后来发现是写的时候没有注意合并的顺序……

//minamoto
#include<bits/stdc++.h>
#define IT vector<node>::iterator
#define fp(i,a,b) for(register int i=a,I=b+1;i<I;++i)
#define fd(i,a,b) for(register int i=a,I=b-1;i>I;--i)
#define go(u) for(register int i=head[u],v=e[i].v;i;i=e[i].nx,v=e[i].v)
using namespace std;
#define getc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
char buf[1<<21],*p1=buf,*p2=buf;
int read(){
    int res,f=1;char ch;
    while((ch=getc())>'9'||ch<'0')(ch=='-')&&(f=-1);
    for(res=ch-'0';(ch=getc())>='0'&&ch<='9';res=res*10+ch-'0');
    return res*f;
}
char sr[1<<21],z[20];int C=-1,Z=0;
inline void Ot(){fwrite(sr,1,C+1,stdout),C=-1;}
void print(int x){
    if(C>1<<20)Ot();if(x<0)sr[++C]='-',x=-x;
    while(z[++Z]=x%10+48,x/=10);
    while(sr[++C]=z[Z],--Z);sr[++C]='\n';
}
const int N=1e5+5;
struct node{
    int c,cnt;
//  inline bool operator <(node b){return cnt==b.cnt?c>b.c:cnt<b.cnt;}
    friend bool operator <(node a,node b){return a.cnt==b.cnt?a.c>b.c:a.cnt<b.cnt;}
    inline node operator +(node b){return {c,cnt+b.cnt};}
};vector<node>v[N];
struct eg{int v,nx;}e[N<<1];int head[N],tot;
inline void add(int u,int v){e[++tot]={v,head[u]},head[u]=tot;}
int rt[N],fa[N],dfn[N],top[N],sz[N],son[N],dep[N],ans[N],n,m,u,vva,c;
void dfs1(int u){
    sz[u]=1,dep[u]=dep[fa[u]]+1;
    go(u)if(v!=fa[u]){
        fa[v]=u,dfs1(v),sz[u]+=sz[v];
        if(sz[v]>sz[son[u]])son[u]=v;
    }
}
void dfs2(int u,int t){
    top[u]=t;if(!son[u])return;dfs2(son[u],t);
    go(u)if(v!=fa[u]&&v!=son[u])dfs2(v,v);
}
inline int LCA(int u,int v){
    while(top[u]!=top[v]){
        if(dep[top[u]]<dep[top[v]])swap(u,v);
        u=fa[top[u]];
    }return dep[u]<dep[v]?u:v;
}
namespace LOLI{
    int L[N<<5],R[N<<5],tot;node v[N<<5];
    inline void init(){fp(i,1,n)rt[i]=++tot;}
    void ins(int p,int l,int r,node val){
        if(l==r)return (void)(v[p]=val+v[p]);int mid=(l+r)>>1;
        if(val.c<=mid)ins(L[p]=L[p]?L[p]:++tot,l,mid,val);
        else ins(R[p]=R[p]?R[p]:++tot,mid+1,r,val);v[p]=max(v[L[p]],v[R[p]]);
    }
    void merge(int x,int y,int l,int r){
//      printf("%d %d %d %d\n",x,y,l,r);
        if(l==r)return (void)(v[x]=v[x]+v[y]);int mid=(l+r)>>1;
        if(L[x]&&L[y])merge(L[x],L[y],l,mid);else if(L[y])L[x]=L[y];
        if(R[x]&&R[y])merge(R[x],R[y],mid+1,r);else if(R[y])R[x]=R[y];
        v[x]=max(v[L[x]],v[R[x]]);
    }
}
void dfs(int u){
//  printf("%d\n",u);
    go(u)if(v!=fa[u])dfs(v),LOLI::merge(rt[u],rt[v],1,1e5);
    for(IT it=v[u].begin();it!=v[u].end();++it)LOLI::ins(rt[u],1,1e5,*it);
    ans[u]=LOLI::v[rt[u]].c;
}
int main(){
//  freopen("testdata.in","r",stdin);
    n=read(),m=read(),LOLI::init();
    fp(i,1,n-1)u=read(),vva=read(),add(u,vva),add(vva,u);
    dfs1(1),dfs2(1,1);
    fp(i,1,m){
        u=read(),vva=read(),c=read();int lca=LCA(u,vva);
        v[u].push_back(node{c,1}),v[vva].push_back(node{c,1});
        v[lca].push_back(node{c,-1}),v[fa[lca]].push_back(node{c,-1});
    }dfs(1);fp(i,1,n)print(ans[i]);return Ot(),0;
}

原文地址:https://www.cnblogs.com/bztMinamoto/p/10023518.html

时间: 2024-10-08 14:45:16

P4556 [Vani有约会]雨天的尾巴(线段树合并)的相关文章

P4556 [Vani有约会]雨天的尾巴 树链剖分 线段树合并

P4556 [Vani有约会]雨天的尾巴 提交2.75k 通过789 时间限制1.00s 内存限制125.00MB 提交代码加入收藏 题目提供者yyy2015c01 难度省选/NOI- 历史分数100 提交记录查看题解 标签 查看算法标签 相关讨论 进入讨论版 查看讨论 推荐题目 查看推荐 展开 题目背景 深绘里一直很讨厌雨天.灼热的天气穿透了前半个夏天,后来一场大雨和随之而来的洪水,浇灭了一切.虽然深绘里家乡的小村落对洪水有着顽固的抵抗力,但也倒了几座老房子,几棵老树被连根拔起,以及田地里的粮

P4556 雨天的尾巴 线段树合并

使用线段树合并,每个节点维护一棵权值线段树,下标为救济粮种类,区间维护数量最多的救济粮编号(下标).所以每个节点答案即为\(tre[rot[x]]\). 然后运用树上点的差分思想,对于分发路径\(u,v\),我们在\(u\)上+1,在\(v\)+1,在\(lca(u,v)\)处-1,在\(fa(lca)\)处-1,最后统计时自底向上做树上前缀和.线段树合并即得当前节点信息. 需要注意的是,在合并时可能会出现\(tre[rot[x]]\)不为\(0\),但是\(sum[rot[x]]\)为\(0\

luogu4556 雨天的尾巴 (线段树合并+差分)

题目背景 深绘里一直很讨厌雨天.灼热的天气穿透了前半个夏天,后来一场大雨和随之而来的洪水,浇灭了一切.虽然深绘里家乡的小村落对洪水有着顽固的抵抗力,但也倒了几座老房子,几棵老树被连根拔起,以及田地里的粮食被弄得一片狼藉.无奈的深绘里和村民们只好等待救济粮来维生.不过救济粮的发放方式很特别.题目描述 首先村落里的一共有n座房屋,并形成一个树状结构.然后救济粮分m次发放,每次选择两个房屋(x,y),然后对于x到y的路径上(含x和y)每座房子里发放一袋z类型的救济粮.然后深绘里想知道,当所有的救济粮发

[Vani有约会]雨天的尾巴

我之前考试是遇到过这题,但是数据范围k<=20,状压就能过. 结果原题范围k<=100000-- 果断线段树合并. 普及线段树合并: 比如两个相同大小的线段树,将b树各个区间上的值合并到a树上,从树根开始合并,然后递归合并左右儿子,有三种情况: (假设现在a树遍历到x点,b树遍历到y点) 1.x,y至少其一未被修改过(语文不好勿喷),则将x变为遍历过的那个. 2.x,y位于叶节点(l==r),则sum[x]+=sum[y]. 3.一般情况,递归处理左右儿子,最后更新当前点. 本题中合并如下:

BZOJ 3307 雨天的尾巴 线段树

题目大意:给定一棵树,有m个操作,每次给一条路径上的每个点分发一个颜色为z的物品,所有操作结束后输出每个点上数量最多的是哪种物品 对于每个操作,我们在x和y上各打上一个插入z的标记,然后在LCA(x,y)和Fa(LCA(x,y))上各打上一个删除z的标记 然后我们对z维护线段树 DFS一遍,对于每个节点进行如下操作: 1.将所有子节点的线段树合并过来 2.处理标记,该插♂入的插♂入,该删除的删除 3.查询最大值作为答案 这样的复杂度是O(mlogm)的 然而跑不过树链剖分什么鬼--是我没离散化的

LG4556 [Vani有约会]雨天的尾巴 动态开点线段树+线段树合并

问题描述 LG4556 题解 对于每一个结点,建立一棵动态开点线段树. 然后自低向上合并线段树. 同时维护整个值域的最大值和最大值位置. \(\mathrm{Code}\) #include<bits/stdc++.h> using namespace std; template <typename Tp> void read(Tp &x){ x=0;char ch=1;int fh; while(ch!='-'&&(ch>'9'||ch<'0'

[树上差分][线段树合并]JZOJ 3397 雨天的尾巴

Description 深绘里一直很讨厌雨天. 灼热的天气穿透了前半个夏天,后来一场大雨和随之而来的洪水,浇灭了一切. 虽然深绘里家乡的小村落对洪水有着顽固的抵抗力,但也倒了几座老房子,几棵老树被连 根拔起,以及田地里的粮食被弄得一片狼藉. 无奈的深绘里和村民们只好等待救济粮来维生. 不过救济粮的发放方式很特别. 首先村落里的一共有n 座房屋,并形成一个树状结构.然后救济粮分m 次发放,每次选择 两个房屋(x,y),然后对于x 到y 的路径上(含x 和y) 每座房子里发放一袋z 类型的救济粮.

bzoj3307雨天的尾巴(可持久化线段树合并)

题目大意: 一颗树,想要在树链上添加同一物品,问最后每个点上哪个物品最多. 解题思路: 假如说物品数量少到可以暴力添加,且树点极少,我们怎么做. 首先在一个树节点上标记出哪些物品有多少,寻找道路上的节点暴力添加. 而如果节点比较多,我们使用树上差分u+1,v+1,lca-1,fa[lca]-1向上求和就得到了答案 而颜色较多呢,和刚才唯一不同就在于向上求和时不要用数组,用线段树合并就好了(记录线段树区间的最大值与最大位置) 在神犇博客那里看到了废点的回收^_^ 代码: 1 #include<qu

Luogu4556 雨天的尾巴 树上差分、线段树合并

传送门 一个套路题-- 树上差分+线段树合并即可 注意一个细节:$pushup$的时候如果最大值为$0$一定要把最大值对应的答案也设为$0$,否则会$WA$第二个点 另外如果这道题空限变小了请不要交这份代码,因为这份代码没有卡空间... 1 #include<bits/stdc++.h> 2 #define mid ((l + r) >> 1) 3 //This code is written by Itst 4 using namespace std; 5 6 inline in