zoukankan      html  css  js  c++  java
  • 洛谷 P3258 [JLOI2014]松鼠的新家 树链剖分+差分前缀和优化

    题面

    题目链接

    P3258 [JLOI2014]松鼠的新家

    题目描述

    松鼠的新家是一棵树,前几天刚刚装修了新家,新家有 $ n $ 个房间,并且有 $ n-1 $ 根树枝连接,每个房间都可以相互到达,且俩个房间之间的路线都是唯一的。天哪,他居然真的住在”树“上。

    松鼠想邀请小熊维尼前来参观,并且还指定一份参观指南,他希望维尼能够按照他的指南顺序,先去 $ a_1 $,再去 $ a_2 $,......,最后到 $ a_n $,去参观新家。可是这样会导致维尼重复走很多房间,懒惰的维尼不停地推辞。可是松鼠告诉他,每走到一个房间,他就可以从房间拿一块糖果吃。

    维尼是个馋家伙,立马就答应了。现在松鼠希望知道为了保证维尼有糖果吃,他需要在每一个房间各放至少多少个糖果。

    因为松鼠参观指南上的最后一个房间 $ a_n $ 是餐厅,餐厅里他准备了丰盛的大餐,所以当维尼在参观的最后到达餐厅时就不需要再拿糖果吃了。

    输入输出格式

    输入格式

    第一行一个整数 $ n $,表示房间个数第二行n个整数,依次描述 $ a_1-a_n $

    接下来 $ n-1 $ 行,每行两个整数 $ x $ , $ y $ ,表示标号x和y的两个房间之间有树枝相连。

    输出格式

    一共 $ n $ 行,第 $ i $ 行输出标号为i的房间至少需要放多少个糖果,才能让维尼有糖果吃。

    输入输出样例

    输入样例:

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

    输出样例:

    1
    2
    1
    2
    1
    

    说明

    【数据范围】

    $ 2 leq n leq 300000 $

    说明

    【时空限制】

    1000ms,128M

    思路

    整理题意,可参考如下理解:

    给定一个n棵结点的树,一个人从给定的起点出发按一定的顺序走至终点;他经过每个点时,该点点权加1 (包括起点但不包括终点)。求最终每点的点权。

    首先我考虑的是每次改变a[i]和a[i+1]之间所有点的点权。可以拿纸模拟一下,会发现这样的问题。

    这样计算,那么样例得到的结果最终为1 3 2 3 2

    由于在a[i]到a[i+1]再到a[i+2]的过程中,按照上面的算法,先改变a[i]和a[i+1]之间所有点的点权,再改变a[i+1]和a[i+2]之间所有点的点权,那么a[i+1]的点权就重复算了一次。故除了起点和终点,其他每个点点权都多算了1,应该减去;而终点也多算了一次,原因是最后一次到达终点时点权不用加1;起点并没有重复,不用减去。

    所以最终算法归纳为:

    1.每次改变a[i]和a[i+1]之间所有点的点权,$ 1 leq i < n $
    2.将除起点外的所有点点权减1

    这样基本上是一道树剖的板子题了吧

    AC代码

    #include<bits/stdc++.h>
    const int maxn=300010;
    using namespace std;
    
    int n,a[maxn];
    int tot,to[maxn<<1],nxt[maxn<<1],head[maxn];
    int dep[maxn],fa[maxn],son[maxn],len[maxn];
    int cnt,nid[maxn],nw[maxn],top[maxn];
    struct SegmentTree
    {
        int l,r,tag,sum;
        #define l(a) tree[a].l
        #define r(a) tree[a].r
        #define m(a) ((l(a)+r(a))>>1)
        #define len(a) (r(a)-l(a)+1)
        #define t(a) tree[a].tag
        #define s(a) tree[a].sum
    }tree[maxn<<2];
    
    void dfs1(int u,int f,int d)
    {
        dep[u]=d;fa[u]=f;len[u]=1;
        for(int i=head[u];i;i=nxt[i])
        {
            int v=to[i];
            if(v==f) continue;
            dfs1(v,u,d+1);
            len[u]+=len[v];
            if(len[v]>len[son[u]]) son[u]=v;
        }
    }
    
    void dfs2(int p,int t)
    {
        nid[p]=++cnt;
        top[p]=t;
        if(!son[p]) return;
        dfs2(son[p],t);
        for(int i=head[p];i;i=nxt[i])
        {
            int v=to[i];
            if(v==fa[p] || v==son[p]) continue;
            dfs2(v,v);
        }
    }
    
    void BuildTree(int p,int l,int r)
    {
        l(p)=l;r(p)=r;
        if(l==r) return;
        BuildTree(p<<1,l,m(p));
        BuildTree(p<<1|1,m(p)+1,r);
    }
    
    void PushDown(int p)
    {
        if(t(p))
        {
            s(p<<1)+=t(p)*len(p<<1);
            s(p<<1|1)+=t(p)*len(p<<1|1);
            t(p<<1)+=t(p);
            t(p<<1|1)+=t(p);
            t(p)=0;
        }
    }
    
    void Change1(int p,int l,int r)
    {
        if(l<=l(p) && r>=r(p))
        {
            t(p)++;
            s(p)+=len(p);
            return;
        }
        PushDown(p);
        if(l<=m(p)) Change1(p<<1,l,r);
        if(r>m(p)) Change1(p<<1|1,l,r);
        s(p)=s(p<<1)+s(p<<1|1);
    }
    
    int Ask(int p,int l,int r)
    {
        if(l<=l(p) && r>=r(p)) return s(p);
        PushDown(p);
        int ans=0;
        if(l<=m(p)) ans+=Ask(p<<1,l,r);
        if(r>m(p)) ans+=Ask(p<<1|1,l,r);
        return ans;
    }
    
    void Change2(int u,int v)
    {
        while(top[u]!=top[v])
        {
            if(dep[top[u]]<dep[top[v]]) swap(u,v);
            Change1(1,nid[top[u]],nid[u]);
            u=fa[top[u]];
        }
        if(dep[u]>dep[v]) swap(u,v);
        Change1(1,nid[u],nid[v]);
    }
    
    int main()
    {
        scanf("%d",&n);
        for(int i=1;i<=n;i++) scanf("%d",&a[i]);
        for(int i=1;i<n;i++)
        {
            int u,v;scanf("%d%d",&u,&v);
            to[++tot]=v;nxt[tot]=head[u];head[u]=tot;
            to[++tot]=u;nxt[tot]=head[v];head[v]=tot;
        }
        dfs1(a[1],a[1],1);
        dfs2(a[1],a[1]);
        BuildTree(1,1,n);
        for(int i=1;i<n;i++) Change2(a[i],a[i+1]);
        for(int i=1;i<=n;i++) printf("%d
    ",Ask(1,nid[i],nid[i])-(i==a[1]? 0:1));
        return 0;
    }
    

    优化

    然而,这个题的修改操作很简单,就是很多个区间上同时加1;而如果用线段树,还会存储一堆区间和,有点浪费。可以考虑差分前缀和的方式

    差分

    如果b是a的差分数组,那么在a的某个[i,j]内同时加上1,等效于b[i]++,b[j+1]--;统计答案时只需要从左到右扫一遍就可以了

    优化后AC代码

    #include<bits/stdc++.h>
    const int maxn=300010;
    using namespace std;
    
    int n,a[maxn];
    int tot,to[maxn<<1],nxt[maxn<<1],head[maxn];
    int dep[maxn],fa[maxn],son[maxn],len[maxn];
    int cnt,nid[maxn],top[maxn];
    int ans[maxn];
    struct SegmentTree
    {
        int l,r,tag,sum;
        #define l(a) tree[a].l
        #define r(a) tree[a].r
        #define m(a) ((l(a)+r(a))>>1)
        #define len(a) (r(a)-l(a)+1)
        #define t(a) tree[a].tag
        #define s(a) tree[a].sum
    }tree[maxn<<2];
    
    void dfs1(int u,int f,int d)
    {
        dep[u]=d;fa[u]=f;len[u]=1;
        for(int i=head[u];i;i=nxt[i])
        {
            int v=to[i];
            if(v==f) continue;
            dfs1(v,u,d+1);
            len[u]+=len[v];
            if(len[v]>len[son[u]]) son[u]=v;
        }
    }
    
    void dfs2(int p,int t)
    {
        nid[p]=++cnt;
        top[p]=t;
        if(!son[p]) return;
        dfs2(son[p],t);
        for(int i=head[p];i;i=nxt[i])
        {
            int v=to[i];
            if(v==fa[p] || v==son[p]) continue;
            dfs2(v,v);
        }
    }
    
    void Change1(int i,int j)
    {
        ans[i]++;ans[j+1]--;
    }
    
    void Change2(int u,int v)
    {
        while(top[u]!=top[v])
        {
            if(dep[top[u]]<dep[top[v]]) swap(u,v);
            Change1(nid[top[u]],nid[u]);
            u=fa[top[u]];
        }
        if(dep[u]>dep[v]) swap(u,v);
        Change1(nid[u],nid[v]);
    }
    
    int main()
    {
        scanf("%d",&n);
        for(int i=1;i<=n;i++) scanf("%d",&a[i]);
        for(int i=1;i<n;i++)
        {
            int u,v;scanf("%d%d",&u,&v);
            to[++tot]=v;nxt[tot]=head[u];head[u]=tot;
            to[++tot]=u;nxt[tot]=head[v];head[v]=tot;
        }
        dfs1(a[1],a[1],1);
        dfs2(a[1],a[1]);
        for(int i=1;i<n;i++) Change2(a[i],a[i+1]);
        for(int i=2;i<=n;i++) ans[i]+=ans[i-1];
        for(int i=1;i<=n;i++) printf("%d
    ",ans[nid[i]]-(i==a[1]? 0:1)); ///判断是否是起点,如果不是起点则-1
        return 0;
    }
    

    总结

    区间加1可以用差分,这样仅仅只用更改两个端点

  • 相关阅读:
    BZOJ4503 两个串
    【挖坟】HDU3205 Factorization
    webpack打包 The 'mode' option has not been set, webpack will fallback to
    echarts js报错 Cannot read property 'getAttribute' of null
    微信支付 get_brand_wcpay_request fail,Undefined variable: openid
    layui动态设置checbox选中状态
    layui 获取radio单选框选中的值
    js 获取数组最后一个元素
    js生成指定范围内的随机数
    layer重复弹出(layui弹层同时存在多个)的解决方法
  • 原文地址:https://www.cnblogs.com/Mercury04/p/9707069.html
Copyright © 2011-2022 走看看