zoukankan      html  css  js  c++  java
  • BZOJ4712洪水——动态DP+树链剖分+线段树

    题目描述

    小A走到一个山脚下,准备给自己造一个小屋。这时候,小A的朋友(op,又叫管理员)打开了创造模式,然后飞到
    山顶放了格水。于是小A面前出现了一个瀑布。作为平民的小A只好老实巴交地爬山堵水。那么问题来了:我们把这
    个瀑布看成是一个n个节点的树,每个节点有权值(爬上去的代价)。小A要选择一些节点,以其权值和作为代价将
    这些点删除(堵上),使得根节点与所有叶子结点不连通。问最小代价。不过到这还没结束。小A的朋友觉得这样
    子太便宜小A了,于是他还会不断地修改地形,使得某个节点的权值发生变化。不过到这还没结束。小A觉得朋友做
    得太绝了,于是放弃了分离所有叶子节点的方案。取而代之的是,每次他只要在某个子树中(和子树之外的点完全
    无关)。于是他找到你。

    输入

     输入文件第一行包含一个数n,表示树的大小。

    接下来一行包含n个数,表示第i个点的权值。
    接下来n-1行每行包含两个数fr,to。表示书中有一条边(fr,to)。
    接下来一行一个整数,表示操作的个数。
    接下来m行每行表示一个操作,若该行第一个数为Q,则表示询问操作,后面跟一个参数x,表示对应子树的根;若
    为C,则表示修改操作,后面接两个参数x,to,表示将点x的权值加上to。
    n<=200000,保证任意to都为非负数

    输出

     对于每次询问操作,输出对应的答案,答案之间用换行隔开。

    样例输入

    4
    4 3 2 1
    1 2
    1 3
    4 2
    4
    Q 1
    Q 2
    C 4 10
    Q 1

    样例输出

    3
    1
    4
     
    考虑没有修改的情况:
    我们可以树形$DP$,$f[i]$表示堵住以$i$为根的子树的最小代价,显然可以得到转移方程$f[i]=min(val[i],sum f[to])$其中$val[i]$表示删除$i$点的代价,$to$表示$i$的子节点。我们设$g[i]$表示点$i$所有轻儿子的$f$之和,那么$f[i]=min(val[i],g[i]+f[son[i]])$其中$son[i]$为$i$的重儿子。我们将后面的$f[son[i]]$展开,那么$f[i]=min(val[i],g[i]+min(val[son[i]],g[son[i]]+f[son[son[i]]]))$。可以发现$f[i]$的最小值就是从$i$开始的连续一段重链的$g$值$+$这段重链链尾的$val$值。我们用图更形象地说明:
     
    其中第一行为重链,下面为他们各自的轻儿子(一个点的所有轻儿子用一个点代表)。
    那么最小值就是一段$g$与一个$val$的和的最小值。这个东西实际上就是固定左端点的最小连续子段和(我们称之为最小连续左端和)。
    我们树链剖分后用线段树维护每条重链的最小连续左端和即可。
    现在考虑有修改操作的情况:
    假设修改点为$x$,那么首先会影响$x$所在重链的最小连续左端和即$x$所在重链链头(假设为$u$)的$f$值,也就会进一步影响$u$的父节点的$g$值及$u$的父节点所在重链的最小连续左端和,以此类推会影响到根节点。我们从$x$节点开始往根方向修改沿途重链上的点维护的信息即可。查询时直接查询以查询点为左端点的最小连续左端和。注意只有修改$x$时修改的是$val$值,其他被修改的点都修改的是$g$值。
    #include<set>
    #include<map>
    #include<queue>
    #include<stack>
    #include<cmath>
    #include<vector>
    #include<bitset>
    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #define ll long long
    using namespace std;
    ll v[200010];
    ll g[200010];
    ll f[200010];
    int tot;
    int n,m;
    int head[200010];
    int next[400010];
    int to[400010];
    int son[200010];
    int top[200010];
    int fa[200010];
    int bot[200010];
    int size[200010];
    int s[200010];
    int pos[200010];
    int q[200010];
    int num;
    int x,y;
    ll z;
    char ch[3];
    struct miku
    {
        ll sum;
        ll mn;
    }t[800010];
    void add(int x,int y)
    {
        tot++;
        next[tot]=head[x];
        head[x]=tot;
        to[tot]=y;
    }
    void dfs(int x)
    {
        size[x]=1;
        for(int i=head[x];i;i=next[i])
        {
            if(to[i]!=fa[x])
            {
                fa[to[i]]=x;
                dfs(to[i]);
                size[x]+=size[to[i]];
                if(size[to[i]]>size[son[x]])
                {
                    son[x]=to[i];
                }
            }
        }
    }
    void dfs2(int x,int tp)
    {
        s[x]=++num;
        q[num]=x;
        f[x]=v[x];
        top[x]=tp;
        if(son[x])
        {
            dfs2(son[x],tp);
            bot[x]=bot[son[x]];
        }
        for(int i=head[x];i;i=next[i])
        {
            if(to[i]!=fa[x]&&to[i]!=son[x])
            {
                dfs2(to[i],to[i]);
                g[x]+=f[to[i]];
            }
        }
        if(!son[x])
        {
            bot[x]=x;
        }
        else
        {
            f[x]=min(f[x],g[x]+f[son[x]]);
        }
    }
    void pushup(int rt)
    {
        int ls=rt<<1;
        int rs=rt<<1|1;
        t[rt].sum=t[ls].sum+t[rs].sum;
        t[rt].mn=min(t[ls].sum+t[rs].mn,t[ls].mn);
    }
    void build(int rt,int l,int r)
    {
        if(l==r)
        {
            int x=q[l];
            pos[x]=rt;
            t[rt].sum=g[x];
            t[rt].mn=v[x];
            return ;
        }
        int mid=(l+r)>>1;
        build(rt<<1,l,mid);
        build(rt<<1|1,mid+1,r);
        pushup(rt);
    }
    void updata(int x)
    {
        int rt=pos[x]>>1;
        while(rt)
        {
            pushup(rt);
            rt>>=1;
        }
    }
    miku query(int rt,int l,int r,int L,int R)
    {
        if(L<=l&&r<=R)
        {
            return t[rt];
        }
        miku ls,rs,res;
        int mid=(l+r)>>1;
        if(L>mid)
        {
            return query(rt<<1|1,mid+1,r,L,R);
        }
        else if(R<=mid)
        {
            return query(rt<<1,l,mid,L,R);
        }
        else
        {
            ls=query(rt<<1,l,mid,L,R);
            rs=query(rt<<1|1,mid+1,r,L,R);
            res.sum=ls.sum+rs.sum;
            res.mn=min(ls.sum+rs.mn,ls.mn);
            return res;
        }
    }
    void change(int x,ll val)
    {
        int now=x;
        while(x)
        {
            ll cnt=query(1,1,n,s[top[x]],s[bot[x]]).mn;
            if(x==now)
            {
                t[pos[x]].mn+=val;
                updata(x);
            }
            else
            {
                t[pos[x]].sum+=val;
                updata(x);
            }
            val=query(1,1,n,s[top[x]],s[bot[x]]).mn-cnt;
            x=fa[top[x]];
        }
    }
    int main()
    {
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
        {
            scanf("%lld",&v[i]);
        }
        for(int i=1;i<n;i++)
        {
            scanf("%d%d",&x,&y);
            add(x,y);
            add(y,x);
        }
        dfs(1);
        dfs2(1,1);
        build(1,1,n);
        scanf("%d",&m);
        for(int i=1;i<=m;i++)
        {
            scanf("%s",ch);
            if(ch[0]=='C')
            {
                scanf("%d%lld",&x,&z);
                change(x,z);
            }
            else
            {
                scanf("%d",&x);
                printf("%lld
    ",query(1,1,n,s[x],s[bot[x]]).mn);
            }
        }
    }
  • 相关阅读:
    ListBox的数据绑定
    GridView中加入新行方法
    一个事务的例子
    用sql语句查询从N条到M条的记录
    用户注册表中日期输入的解决方案
    对分页控件进行分页的封装
    我的触发器
    缓存DataSet以提高性能
    网站访问统计在Global.asax中的配置
    给表格控件绑定数据库内容的封装
  • 原文地址:https://www.cnblogs.com/Khada-Jhin/p/10001023.html
Copyright © 2011-2022 走看看