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('\n');
    }
}

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('\n');
    }
}

int main(){
//  freopen("testdata.in","r",stdin);
//  freopen("testdata.out","w",stdout);
    readdata();
    work();
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/Mandy-H-Y/p/11779289.html