P4438 [HNOI/AHOI2018]道路 树DP

题意:

戳这里

分析:

这个题意好阴间,转化一下大概就是,给定一颗二叉树,保证有 \(n\) 个叶子结点,\(n-1\) 个非叶子结点,每个点有两种边,求给定式子的最小值

  • 假做法

我原本以为这个式子拆开之后可以分别计算贡献

\(c_u(a_u+x)(b_u+y)=a_ub_uc_u+c_ua_uy+a_ub_ux+c_uxy\)

开一个三元组分别统计 \(x,y\) 状态下的三个参数,但是这个贡献没有办法判断最优所以没法转移

  • 正解:

我们发现刚才的式子没法转移是因为我们没有办法考虑加 一条公路/铁路带来的影响 ,那么我们的 DP 就需要避免这种情况,直接把一个点的所有贡献统计完,这也就要求我们必须知道它到根节点的路径上两种边的数目,而且转移还必须从下向上,因此我们得出另一种DP的方式 , \(f[u][i][j]\) 表示 \(u\) 到根节点的路径上有 \(i\) 条公路 \(j\) 条铁路,易得转移式如下:

\(f[u][i][j]=min(f[lc[u]][i][j]+f[rc[u][i]][j+1],f[lc[u]][i+1][j]+f[rc[u]][i][j])\)

最后的答案就是 \(f[1][0][0]\)

但是做到这里还没有完,如果完全按照这种方式 DP 的话空间复杂度是 \(2n*40^2\) 大约在 \(3e7\) 级别的 \(long long\) 会炸空间,所以我们要优化 DP ,,我们发现 DP 的时候只与 一条链有关,所以我们给链上的每一个点重标号,这样 DP 数组的第一维只要开到树上最长链的长度 \(2*40\) 就够了

代码:

#include<bits/stdc++.h>
#define inl inline
#define reg register

using namespace std;

namespace zzc
{
    typedef long long ll;
    inl ll read()
    {
        ll x=0,f=1;char ch=getchar();
        while(!isdigit(ch)) {if(ch=='-')f=-1;ch=getchar();}
        while(isdigit(ch)) {x=x*10+ch-48;ch=getchar();}
        return x*f;
    }

    const ll maxn = 1e5+5;
    ll n;
    ll lc[maxn],rc[maxn],a[maxn],b[maxn],c[maxn],dfn[maxn],dep[maxn];
    long long f[105][50][50];
    
    void dfs(ll u,ll k)
    {
        dfn[u]=k;
        if(lc[u])
        {
            dep[lc[u]]=dep[rc[u]]=dep[u]+1;
            dfs(lc[u],k+1);dfs(rc[u],k+2);
            for(ll i=0;i<=dep[u];i++)
            {
                for(ll j=0;j<=dep[u];j++)
                {
                    f[dfn[u]][i][j]=min(f[dfn[lc[u]]][i][j]+f[dfn[rc[u]]][i][j+1],f[dfn[lc[u]]][i+1][j]+f[dfn[rc[u]]][i][j]);
                }
            }
        }
        else
        {
            for(ll i=0;i<=dep[u];i++)
            {
                for(ll j=0;j<=dep[u];j++)
                {
                    f[dfn[u]][i][j]=1ll*c[u]*(a[u]+i)*(b[u]+j);
                }
            }
        }
    }

    void work()
    {
        memset(f,0x3f,sizeof(f));
        n=read();
        for(ll i=1;i<n;i++)
        {
            lc[i]=read();rc[i]=read();
            if(lc[i]<0) lc[i]=-lc[i]+n-1;
            if(rc[i]<0) rc[i]=-rc[i]+n-1;
        }
        for(ll i=n;i<=2*n-1;i++) a[i]=read(),b[i]=read(),c[i]=read();
        dfs(1,0);
        printf("%lld\n",f[dfn[1]][0][0]);
    }

}

int main()
{
    zzc::work();
    return 0;
}
posted @ 2021-01-26 11:28  youth518  阅读(100)  评论(0编辑  收藏  举报