hdu 5029 Relief grain 题解 (树链剖分,线段树,思维)

原题链接:
hdu

题意简述

给定一颗树,和一些操作。一次操作给定 u , v , c u,v,c u,v,c,表示从 u u u v v v的路径上的点都会新加一种颜色 c c c。最后询问每个点上那种颜色有的最多。

数据

输入

多组数据。对于每组数据:

n m
//点数,操作数(n,m<=10^5)
u v
u v
...
u v
//n-1行,表示u和v之间有边。1<=u,v<=n
u v c 
u v c
...
u v c
//m行,表示操作.1<=u,v<=n,1<=c<=10^5
输入0 0停止
输出

多组数据。对于每组数据:

ans1 
ans2 
... 
ansn
//输出每个点的答案
样例

输入

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

输出

1
2
2
3
3
0
2

思路

又是个树剖。树剖的题目,要先考虑区间怎么做,再考虑丢到树上。

part1. 区间

看到题目中说是到最后才统一询问一次,就考虑差分做了。

正常的差分:当我们对区间 [ l , r ] [l,r] [l,r]染颜色 c c c时,就在 l − 1 l-1 l1上加上 1 1 1,在 r r r上加上 − 1 -1 1

然后我们就会发现,光 1 1 1 − 1 -1 1不行,我们还要区分开颜色。观察到颜色的范围是 100000 100000 100000。所以。。。

  1. 再加一维。
    那显然是不行的。空间复杂度 O ( n 2 ) O(n^2) O(n2),开 1 0 10 10^{10} 1010的数组,显然爆了
  2. 同 一 个 差 分 数 组 维 护 不 同 的 颜 色 \color{#ff0000}{同一个差分数组维护不同的颜色} 。我们暴力模拟一下上面的那个思路。设差分数组叫 r e c rec rec。那么对于一次染色操作,就是:++rec[c][l-1],--rec[c][r]。然后我们发现,除了必要的 l − 1 l-1 l1 r r r之外,剩下的如果打成数对,就是 ( c , 1 ) (c,1) (c,1) ( c , − 1 ) (c,-1) (c,1)。那么,我们能否把这个东西放在一个差分数组上加呢?此时已经有点显然了。当然,如果你智商足够,不用我提示,你也很快就能想到一个方法:把它们变成 c c c − c -c c
    !!!
    是不是很神奇。。。但是这样就不能是加上去了,加上去的话我们只能得到一个和。但是我们要得到的是每个数有多少。那么我们就不能用一个简单的 i n t int int维护差分数组了,而是 v e c t o r &lt; i n t &gt; vector&lt;int&gt; vector<int>。当然原来的 + + , − − ++,-- ++,都要变成 p u s h _ b a c k push\_back push_back

那么我们如何求答案捏。。。我们只要把原来的 c c c − c -c c拆开,拆成原来的 ( c , 1 ) (c,1) (c,1) ( c , − 1 ) (c,-1) (c,1)的形式。这个珂以用绝对值函数 a b s abs abs实现。

根据初中学的知识,一个数 x x x的绝对值就是数字部分,而 x ∣ x ∣ \frac{x}{|x|} xx就是 x x x的符号部分。

然后我们就开一个数组 a n s ans ans a n s [ c ] ans[c] ans[c]表示当前枚举到的前缀中,有多少种 c c c颜色。然后 i i i 1 1 1 n n n枚举。对于 r e c [ i ] rec[i] rec[i]中的每一个数,我们把它拆开,然后ans[数字部分]+=符号部分然后第 i i i个位置的答案呢。。。也就是 a n s ans ans中的最大值。
然后我们会发现这个求最大值还要 O ( n ) O(n) O(n),不就 n 2 n^2 n2了么。。。不,别急啊,上线段树。考虑到标记的总数是 O ( 2 m ) O(2m) O(2m),所以 O ( l o g n ) O(logn) O(logn) 2 m 2m 2m次, O ( l o g n ) O(logn) O(logn) n n n次,就是 O ( ( n + 2 m ) l o g n ) O((n+2m)logn) O((n+2m)logn),是显然能过的。

然后最后不是说要求哪个颜色出现最多,而不是出现几次么。。。
我艹真tm毒瘤。但是这个对于线段树来说,就是个二分。。。(解释见代码)

part2. 放到树上

如何放到树上呢。。。
只要在树链剖分的时候,不断的加标记即珂。对于我们现在走到的 u u u,我们设最上面是 t o p [ u ] top[u] top[u],那就是 r e c [ D F S i d [ t o p [ u ] ] ] . p u s h _ b a c k ( c ) rec[DFSid[top[u]]].push\_back(c) rec[DFSid[top[u]]].push_back(c) r e c [ D F S i d [ u ] ] . p u s h _ b a c k ( − c ) rec[DFSid[u]].push\_back(-c) rec[DFSid[u]].push_back(c)。其中 D F S i d DFSid DFSid D F S DFS DFS序。

然后最后记得,你枚举 i i i 1 1 1 n n n,计算标记前缀和的时候,你求的答案并不是点 i i i的答案,而是点 x x x的答案,其中 x x x D F S DFS DFS序是 i i i。那么我们就要处理一个 D F S i d DFSid DFSid数组的反函数(俗一点说,这个数组就是求那个数的 D F S DFS DFS序是它。在代码里面叫 O r i Ori Ori,即 o r i g i n origin origin的简写)。然后记录答案即珂。

代码:

#include<bits/stdc++.h>
using namespace std;
namespace Flandle_Scarlet
{
    #define N 200100//别怪我开一倍,我只是RE多了,有心理阴影了
    class Graph//图
    {
        public:
            int head[N];
            int EdgeCount;
            struct Edge
            {
                int To,Label,Next;
            }Ed[N<<1];

            void clear()
            {
                memset(Ed,-1,sizeof(Ed));
                memset(head,-1,sizeof(head));
                EdgeCount=0;
            }
            void AddEdge(int u,int v,int w)
            {
                ++EdgeCount;
                Ed[EdgeCount]=(Edge){v,w,head[u]};
                head[u]=EdgeCount;
            }

            int Start(int u)
            {
                return head[u];
            }
            int To(int u)
            {
                return Ed[u].To;
            }
            int Label(int u)
            {
                return Ed[u].Label;
            }
            int Next(int u)
            {
                return Ed[u].Next;
            }
    }G;void Add(int u,int v,int w){G.AddEdge(u,v,w);G.AddEdge(v,u,w);}
    class SegmentTree//线段树
    //单点修改,区间求最值
    {
        public:
            struct node
            {
                int l,r;
                int x;
            }tree[N<<2];
            #define ls index<<1
            #define rs index<<1|1

            #define L tree[index].l
            #define R tree[index].r
            #define X tree[index].x

            #define lL tree[ls].l
            #define lR tree[ls].r
            #define lX tree[ls].x

            #define rL tree[rs].l
            #define rR tree[rs].r
            #define rX tree[rs].x
            void Update(int index)
            {
                X=max(lX,rX);
            }
            void BuildTree(int l,int r,int index)
            {
                L=l,R=r,X=0;
                if (l==r) return;
                int mid=(l+r)>>1;
                BuildTree(l,mid,ls);
                BuildTree(mid+1,r,rs);
                Update(index);
            }
            void Add(int pos,int c,int index)
            {
                if (pos<L or R<pos) return;
                if (L==R)
                {
                    X+=c;
                    return;
                }
                Add(pos,c,ls);
                Add(pos,c,rs);
                Update(index);
            }
            int Query(int mx,int index)
            //Query函数:求哪个位置是满足条件的最大值。在这个基础上还保证这个位置最靠前。
            /*
            说好要来解释呢QωQ。那好,我来解释了。
            我们把区间一分为二(这个线段树已经帮我们做好了)
            优先考虑左半边(因为要答案最靠前)。如果左半边的区间最大=mx,那么就说明左半边有相应的最大值。所以我们就去左半边找了。此时我们就不用考虑右半边了,因为肯定都没有左半边的答案靠前
            如果左半边没有答案,即区间最值!=mx,那就只能去右半边找了。。。
            这样就能找到了呢QωQ
            */
            {
                if (L==R) return L;
                if (lX==mx) return Query(mx,ls);
                else Query(mx,rs);
            }
    }T;
    int deep[N],size[N],son[N],fa[N];
    void DFS1(int u,int f)
    {
        fa[u]=f;
        deep[u]=(f==-1)?1:deep[f]+1;
        size[u]=1;

        son[u]=-1;
        int Max=-1;
        for(int i=G.Start(u);~i;i=G.Next(i))
        {
            int v=G.To(i);
            if (v!=f)
            {
                DFS1(v,u);
                size[u]+=size[v];
                if (size[v]>Max)
                {
                    Max=size[v];
                    son[u]=v;
                }
            }
        }
    }
    int DFSid[N];int cnt=0;
    int Ori[N];
    int top[N];
    void DFS2(int u,int topu)
    {
        top[u]=topu;
        DFSid[u]=++cnt;
        Ori[cnt]=u;

        if (son[u]==-1) return;
        DFS2(son[u],topu);
        for(int i=G.Start(u);~i;i=G.Next(i))
        {
            int v=G.To(i);
            if (v!=son[u] and v!=fa[u])
            {
                DFS2(v,v);
            }
        }
    }//【模板】树链剖分

    vector<int>rec[N];//差分数组
    void RAdd(int l,int r,int c)
    {
        rec[l].push_back(c);
        rec[r+1].push_back(-c);
        //封装成函数了呢QωQ
    }
    void PathAdd(int u,int v,int c)
    {
        while(top[u]!=top[v])
        {
            if (deep[top[u]]<deep[top[v]]) swap(u,v);
            RAdd(DFSid[top[u]],DFSid[u],c);
            //只要不断的区间加值就珂以了
            u=fa[top[u]];
        }
        if (deep[u]>deep[v]) swap(u,v);
        RAdd(DFSid[u],DFSid[v],c);
    }//路径修改
    int n,q;
    void Input()
    {
        for(int i=1;i<n;++i)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            Add(u,v,1);
        }
    }

    int ans[N];
    int sign(int x)//求x符号部分
    {
        return (x>0)?1:-1;
        //是珂以x/abs(x)的,但是。。。这样不是更快么。。。
    }
    void Soviet()
    {
        DFS1(1,-1);
        DFS2(1,1);
        T.BuildTree(1,100010,1);//别忘了build。。。
        for(int i=1;i<=q;++i)
        {
            int u,v,c;
            scanf("%d%d%d",&u,&v,&c);
            PathAdd(u,v,c);
        }
        for(int i=1;i<=n;++i)
        {
            for(int j=0;j<rec[i].size();++j)
            {
                int col=rec[i][j];
                T.Add(abs(col),sign(col),1);
            }
            if (T.tree[1].x==0) ans[Ori[i]]=0;//没有颜色。。。那就只能是0了。。。
            else ans[Ori[i]]=T.Query(T.tree[1].x,1);//否则就询问一下是哪种颜色
            //记得是ans[Ori[i]]=...
        }
        for(int i=1;i<=n;++i)
        {
            printf("%d\n",ans[i]);
        }
    }

    void InitAll()
    {
        G.clear();
        for(int i=0;i<N;++i)
        {
            rec[i].clear();
        }

        cnt=0;

        memset(DFSid,0,sizeof(DFSid));
        memset(Ori,0,sizeof(Ori));
        memset(ans,0,sizeof(ans));
        memset(top,0,sizeof(top));
        memset(deep,0,sizeof(deep));
        memset(son,0,sizeof(son));
        memset(size,0,sizeof(size));
        memset(fa,0,sizeof(fa));
    }
    void IsMyWife()
    {
        if (0)
        {
            freopen("","r",stdin);
            freopen("","w",stdout);
        }
        while(~scanf("%d%d",&n,&q))
        {
            if (n==0 and q==0) break;
            InitAll();
            Input();
            Soviet();
        }
    }
};
int main()
{
    Flandle_Scarlet::IsMyWife();
    return 0;
}

回到总题解界面

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值