zoukankan      html  css  js  c++  java
  • [bzoj4712]洪水_动态dp

    洪水 bzoj-4712

    题目大意:给定一棵$n$个节点的有根树。每次询问以一棵节点为根的子树内,选取一些节点使得这个被询问的节点包含的叶子节点都有一个父亲被选中,求最小权值。支持单点修改。

    注释:$1le nle 2cdot 10^5$。保证任意时刻所有节点的权值为正整数。


    想法:显然这是一道动态$dp$的题。

    如果没有单点修改操作,我们直接树形$dp$:

      状态:$f_i$表示以$i$为根的答案。

      转移:$f_i=max(a_i,sumlimits f_j)$。

    现在有了单点修改操作,我们像正常的动态树形dp一样树剖。

    维护$g_i=sumlimits f_j$其中,$j$是$i$的虚儿子。

    这样的话$f_i=max(a_i,g_i+f_{i+1})$因为重儿子在树剖中紧挨着父亲。

    进而我们考虑一条重链上:因为每个节点的权值都是正的,所以一个叶子节点只有一个祖先会被选中。

    我们考虑当前节点所在重链的链底(显然是一个叶子),重链上一定有且仅有一个节点被选中,不妨设为$x$,那么代价就是$a_x+sumlimits_{j=i}^{x-1} g_j$。

    就是最小前缀和的形式。

    所以询问就是询问一条重链上的最小前缀和。

    接下来考虑单点修改:

    显然单点修改不会影响重链上的$g$值,只需要在线段树上单点修改即可。

    它只会影响每个重链的父亲。那么我们求出了当前重链链头的$f$值,紧接着更新重链链头的父亲的$g$,以此类推。

    用线段树维护最小前缀和,同时需要维护区间和。

    Code

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #define N 200010 
    #define lson l,mid,x<<1
    #define rson mid+1,r,x<<1|1
    using namespace std; typedef long long ll;
    struct Node
    {
    	ll sum,ls;
    	Node() {}
    	Node(ll g,ll v){sum=g; ls=v;}
    	inline Node operator +(const Node &a) const
    	{
    		return Node(sum+a.sum,min(sum+a.ls,ls));
    	}
    }a[N<<2],w[N];
    int head[N],to[N<<1],nxt[N<<1],cnt,fa[N],size[N],top[N],down[N],dic[N],tot,n;
    ll v[N],f[N],g[N];
    char str[5];
    inline void add(int x,int y) {to[++cnt]=y; nxt[cnt]=head[x]; head[x]=cnt;}
    void dfs1(int x)
    {
    	size[x]=1;
    	for(int i=head[x];i;i=nxt[i]) if(to[i]!=fa[x])
    		fa[to[i]]=x,dfs1(to[i]),size[x]+=size[to[i]];
    }
    void dfs2(int x,int c)
    {
    	int k=0;
    	top[x]=c,dic[x]=++tot,down[x]=x,f[x]=v[x];
    	for(int i=head[x];i;i=nxt[i]) if(to[i]!=fa[x]&&size[to[i]]>size[k])
    	{
    		k=to[i];
    	}
    	if(k)
    	{
    		dfs2(k,c),down[x]=down[k];
    		for(int i=head[x];i;i=nxt[i])
    			if(to[i]!=fa[x]&&to[i]!=k)
    				dfs2(to[i],to[i]),g[x]+=f[to[i]];
    		f[x]=min(f[x],f[k]+g[x]);
    	}
    	w[dic[x]]=Node(g[x],v[x]);
    }
    inline void pushup(int x) {a[x]=a[x<<1]+a[x<<1|1];}
    void build(int l,int r,int x)
    {
    	if(l==r)
    	{
    		a[x]=w[l];
    		return;
    	}
    	int mid=(l+r)>>1;
    	build(lson); build(rson);
    	pushup(x);
    }
    void updatev(int p,ll v,int l,int r,int x)
    {
    	if(l==r)
    	{
    		a[x].ls+=v;
    		return;
    	}
    	int mid=(l+r)>>1;
    	if(p<=mid) updatev(p,v,lson);
    	else updatev(p,v,rson);
    	pushup(x);
    }
    void updateg(int p,ll g,int l,int r,int x)
    {
    	if(l==r)
    	{
    		a[x].sum+=g;
    		return;
    	}
    	int mid=(l+r)>>1;
    	if(p<=mid) updateg(p,g,lson);
    	else updateg(p,g,rson);
    	pushup(x);
    }
    Node query(int b,int e,int l,int r,int x)
    {
    	if(b<=l&&r<=e) return a[x];
    	int mid=(l+r)>>1;
    	if(e<=mid) return query(b,e,lson);
    	else if(b>mid) return query(b,e,rson);
    	else return query(b,e,lson)+query(b,e,rson);
    }
    inline void modify(int x,ll v)
    {
    	int y=x;
    	ll t;
    	while(x)
    	{
    		t=query(dic[top[x]],dic[down[x]],1,n,1).ls;
    		if(x==y) updatev(dic[x],v,1,n,1);
    		else updateg(dic[x],v,1,n,1);
    		v=query(dic[top[x]],dic[down[x]],1,n,1).ls-t,x=fa[top[x]];
    	}
    }
    int main()
    {
    	int m,x,y;
    	ll z;
    	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);
    	dfs1(1),dfs2(1,1);
    	build(1,n,1);
    	scanf("%d",&m);
    	while(m--)
    	{
    		scanf("%s%d",str,&x);
    		if(str[0]=='C')scanf("%lld",&z),modify(x,z);
    		else printf("%lld
    ",query(dic[x],dic[down[x]],1,n,1).ls);
    	}
    	return 0;
    }
    

    小结:无。

  • 相关阅读:
    卷积池化函数
    keyring源码加密解密函数分析
    mysql语句批量产生大量测试数据
    InnoDB表存储结构及keyring加密
    cmake安装使用
    神经网络推导
    C#基础 集合
    C#基础 数组
    C#基础 类
    C#基础 for 穷举、迭代
  • 原文地址:https://www.cnblogs.com/ShuraK/p/10022621.html
Copyright © 2011-2022 走看看