zoukankan      html  css  js  c++  java
  • 浅谈换根DP

    浅谈换根DP

    本篇随笔浅谈一下算法竞赛中的换根DP。


    换根DP概念

    换根DP其实是树形DP的一种延伸技巧或者说是方法。

    它的使用范围是,对树上的每个点跑树形DP。这样的话,不用换根DP一点一点跑的复杂度就是(O(n^2)),必炸。那么换根DP应运而生。简单来讲,就是我们会通过推理发现,我们先以一个选定节点跑出来的最优解,通过另一个转移方程,就可以得出与他有关系的其他节点的答案。也就是说,我们相当于进行了两次DP,第一次的树形DP可以算作一种预处理,第二次的DP就是换根DP。其根本奥义就是用(O(2N))的复杂度完成了(O(N^2))的问题。


    换根DP例题

    POJ 3585

    题目传送门

    题解传送门

    让我们用一道例题来更深理解换根DP~

    题目大意:

    有一棵有(n)个节点、(n-1)条边的无根树,每边有一流量限制。令某一节点为根节点,向根节点灌水,最终从叶子节点流出的水量和为这一节点的最大流量。问:在做根节点的所有节点中,最大的最大流量是多少?


    题解:

    很容易想到这个某个节点的最大流量可以用树形DP来维护,但是因为一次树形DP是(O(n))的复杂度,如果有(n)个点,那么其复杂度就是(O(n^2))的,(nle 2*10^5),还是多组数据,必炸无疑。

    那么就不能暴力地在每个节点都跑一次树形DP,即需要一种不需要每次都跑的船新操作。

    我们叫他换根DP。我的理解就是,树形DP+换根。

    俗称扭一扭,因为在换根的过程中,树的形态发生了扭转。

    那么我们考虑,用一次树形DP作为信息的预处理,然后之后的答案能否通过预处理,使用换根DP来维护呢?

    PS:先讲树形DP预处理。

    一般来讲,树的形态固定的情况下,才可以把边权转点权(把边权值给儿子,比如树链剖分等等,比较常见的操作)。但换根DP因为树的形态会扭,所以不适合把边权转点券。那么我们DP设置的状态就需要以边作维护。

    状态设置为:(sum[x])表示以(x)点为根的子树所能提供的最大流量和,那么显然,儿子节点对于父亲的贡献就是这个(sum[x])和父亲到儿子的边权的较小值。比如下图(以1为根),(sum[4]=15),但是(4)号点对答案的贡献其实是13,因为被限制了。

    所以转移方程就是:

    [sum[x]=Sigma_{yin son[x]} min(sum[y],val[i]) ]

    需要注意的是初值,叶子节点的(sum)值应该为0,所以转移的时候应该从倒数第二层节点开始转,这个处理我们可以通过特判解决。

    于是我们处理出了一个以(1)为根的(sum)数组,答案就是(sum[1])

    然后就是扭一扭的过程,先上图。

    比如,以1为根的情况和以4为根的情况:(如图)

    我们发现,4-3-5这棵子树的信息是没有变化的,只是原先1是4的儿子,现在儿子翻身当爹了而已,也就是,只有以1为根的子树的信息需要重新统计。我们又发现,1有很多儿子,其中只有4当了爹,其他的儿子依然是儿子,所以只需要把1之前与4的关系断掉,进行重新统计。也就是说,原来的(sum[1])要减去(sum[4])和它俩之间的边权的较小值,也就是13。成为新的(sum[1])

    然后在新的根节点4上加上新的(sum[1])即可。

    这个扭一扭的过程可以通过第二次深搜来实现。

    需要注意的细节是,当我们进行到叶子节点的时候,需要进行特殊判断,很容易得出,在叶子节点和非叶子节点的转移方程是不一样的。

    详见代码:

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    const int maxn=2*1e5+10;
    int n;
    int tot,to[maxn<<1],nxt[maxn<<1],val[maxn<<1],head[maxn];
    int sum[maxn<<1],dp[maxn<<1],du[maxn],ans;
    void add(int x,int y,int z)
    {
        to[++tot]=y;
        val[tot]=z;
        nxt[tot]=head[x];
        head[x]=tot;
    }
    void dfs1(int x,int f)
    {
        for(int i=head[x];i;i=nxt[i])
        {
            int y=to[i];
            if(y==f)
                continue;
            dfs1(y,x);  
            if(du[y]>1)
                sum[x]+=min(val[i],sum[y]);
            else
                sum[x]+=val[i];
        }
    }
    void dfs2(int x,int f)
    {
        for(int i=head[x];i;i=nxt[i])
        {
            int y=to[i];
            if(y==f)
                continue;
            if(du[x]==1)//leaf
                dp[y]=sum[y]+val[i];
            else
                dp[y]=sum[y]+min(dp[x]-min(sum[y],val[i]),val[i]);
            dfs2(y,x);
        }
    }
    void clean()
    {
        tot=0;
        ans=-1;
        memset(sum,0,sizeof(sum));
        memset(du,0,sizeof(du));
        memset(dp,0,sizeof(dp));
        memset(to,0,sizeof(to));
        memset(nxt,0,sizeof(nxt));
        memset(head,0,sizeof(head));
        memset(val,0,sizeof(val));
    }
    int main()
    {
        int t;
        scanf("%d",&t);                    
        while(t--)
        {
            clean();
            scanf("%d",&n);
            for(int i=1;i<n;i++)
            {
                int x,y,z;
                scanf("%d%d%d",&x,&y,&z);
                add(x,y,z);
                add(y,x,z);
                du[x]++;
                du[y]++;
            }
            dfs1(1,0);
            dp[1]=sum[1];
            dfs2(1,0);
            for(int i=1;i<=n;i++)
                ans=max(ans,dp[i]);
            printf("%d
    ",ans);
        }
        return 0;
    }
    

    这就是换根DP啦~

  • 相关阅读:
    svn导入项目和部署方面的相关问题
    JDK版本会影响项目部署
    在HTML中限制input 输入框只能输入纯数字
    mui-下拉刷新
    抽象工厂模式(Abstract Factory)C#实例
    C++基础-结构体伪函数与多线程(void operator()(int))
    Android基础-Activity生命周期
    Android基础-Adapter适配器生成对话框
    Android基础-弹窗对话框(popup)
    Android基础-自定义对话框
  • 原文地址:https://www.cnblogs.com/fusiwei/p/13615814.html
Copyright © 2011-2022 走看看