zoukankan      html  css  js  c++  java
  • 树形 dp/换根 dp

    树形dp

    树形动归一般是依赖于dfs的,根据动归的后效性,父节点的状态一般都依赖子节点的状态以某种方式转移而来

    换根的p2015

    [设f[i][j]表示i的子树上保留j条边最多苹果数\ f[i][j]=max(f[i][j],f[left][j]+e[left].apple+f[right][k-j]+e[right].apple)\ e[i].apple表示i条树枝的苹果数 ]

    p2279

    [状态表示f[x][0]:覆盖到x的爷爷和x整棵子树(向上2层),最少个数\ f[x][1]:覆盖到x的父亲和x子树(向上一层)\ f[x][2]:覆盖到x整颗子树(向上0层)\ f[x][3]:覆盖x的儿子及其子树(向上-1层)\ f[x][4]:覆盖所有x的孙子及其子树(向上-2层)\ 显然f[x][i]一定包含f[x][i+1],y.z是x的儿子\ f[x][0]=1+sum f[y][4](因为i可以覆盖到向上2层,所以它自己必须是消防站~显然)\ x的儿子中有一个一定覆盖的爷爷,同时覆盖到兄弟(因为y一定是选了),其他的儿子只需要覆盖的自己的儿子即可\f[x][1]=min(f[y][0]+sum f[z][3](y!=z))\ f[x][2]=min(f[y][1]+sum[z][2])有一个儿子覆盖到父亲,但无法覆盖到y的兄弟,所以其他儿子要覆盖到自己\ f[x][3]=sum f[y][2]让每个儿子覆盖到自己即可\ f[x][4]=sum f[y][3]让每个儿子覆盖到自己的儿子\ ]

    P1122 最大子树和

    [设f[i][0]为被当前这个点保安控制的点\ 显然f[i][0]=sum min(f[son[i]][0],f[son[i]][1],f[son[i][2])+val[i]\ f[i][1]为被当前这个点的儿子控制的点\ 显然f[i][1]=sum min(f[son[i][0],f[son[i]][1])如果选择的全部都是f[son[i]][1],\要再加上min(f[son[i]][0]-f[son[i]][1])\ f[i][2]为被当前这个点的fa控制的点\ 这个有点麻烦f[i][2]=sum min(f[son[i]][0],f[son[i]][1])我们不妨这样理解,对于i节点我们让它\的父亲节点fa覆盖它,那么根据我们的状态设计,此时必须要满足以i的儿子son[i]为根的子树\之中所有点已经被覆盖那么这时就转化为一个子问题,要让y子树满足条件,只有两种决策:要么son[i]\被son[i]的儿子覆盖,要么被son[i]自己覆盖(即选择son[i]节点)\,只需要在son[i]的这两种状态取min累加就可以了 ]

    对于(f[i][1])的转移,luogu大佬有详细解释:(这位大佬)(\_\_\_new2zy\_\_\_)

    我们可以这样理解,此时既然要保证x点是被自己的儿子覆盖的,那么如果此时y子树已经满足了全部被覆盖,但是y此时被覆盖的状态却是通过y节点自己的儿子达到的,那么x就没有被儿子y覆盖到,那么我们不妨推广一下,如果x所有的儿子y所做的决策都不是通过选择y点来满足条件,那么我们就必须要选择x的一个子节点y,其中y满足(f[y][0]-f[y][1])最小,并把这个最小的差值累加到(f[x][1])中去这样才能使得x点被自己的儿子覆盖**,状态(f[x][1])也才能合理地得到转移

    就是这样1代表选了该点,0没选,假设问号为根节点,0为枝条,1为叶子,这样显然不行,所以取最小花费的点,加入到花费,明白了吧

    ?

    0 0 0 0

    1 1 1 1

    //代码哥哥:
    #include<cstdio>
    #define maxn 1520
    #define int long long
    using namespace std;
    inline int min(int a,int b){return a<b?a:b;}
    inline int read(){
        int p=0,f=1;char c=getchar();
        while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
        while(c>='0'&&c<='9'){p=p*10+c-'0';c=getchar();}
        return f*p;}
    struct edge{
    	int to,next;
    }e[maxn<<1];
    int n,tot=0,head[maxn<<1],val[maxn]; 
    int f[maxn][4];
    inline void add(int x,int y)//加边 
    {
    	tot++;
    	e[tot].next=head[x];
    	head[x]=tot;
    	e[tot].to=y;
    }
    inline int treedp(int u,int fa){
    	f[u][0] = val[u];
    	int sum = 0,mincost = 0x777777f;
    	for(int i = head[u];i;i=e[i].next){
    		int y=e[i].to;
    		if(y==fa) continue;
    		treedp(y,u);
    		int az = min(f[y][0],f[y][1]);
    		f[u][0] += min(f[y][2],az);
    		f[u][2] += az;
    		if(f[y][0]<f[y][1]) sum++;//如果选择儿子节点更优,选上,计数器sum++,证明选过f[y][0] 
    		else mincost=min(mincost,f[y][0]-f[y][1]);
    		f[u][1] += az;
    	}
    	if(!sum)f[u][1] += mincost;
    }
    signed main(){
    	n=read();
    	for(int i=1;i<=n;i++)
    		{
    			int x=read();
    			val[x]=read();
    			int num=read();
    			while(num>0)
    				{
    					int y=read();
    					add(x,y);
    					add(y,x);
    					num--;
    				}
    		}
    	treedp(1,0);
    	printf("%d",min(f[1][0],f[1][1]));
    }
    

    换根dp一般分为三个步骤

    1、先指定一个根节点
    2、一次dfs统计子树内的节点对当前节点的贡献
    3、一次dfs统计父亲节点对当前节点的贡献并合并统计最终答案

    二次扫描与换根法:

    (f[i]表示以u为根的树的深度和,size[i]表示以i为根子树的结点个数)

    (f[v]=f[u]-size[x]+n-size[x]=f[u]+n-2*size[x])

    本来是以u为根的树,变成以儿子v为根的树,

    那么v的所有结点的深度都会减1,深度和就会减少size[v],

    同样地,所有不在v的子树上的结点的深度都会+1,深度和就会加上n - size[v],

  • 相关阅读:
    RabbitMq环境搭建
    Springboot集成quartz
    java8时间工具类
    AngularJS学习笔记之directive——scope选项与绑定策略
    理解$watch ,$apply 和 $digest --- 理解数据绑定过程
    AngularJS中service,factory,provider的区别
    AngularJS的Filter用法详解
    Angular.js中使用$watch监听模型变化
    history
    data-*
  • 原文地址:https://www.cnblogs.com/shikeyu/p/13323286.html
Copyright © 2011-2022 走看看