题解:[HNOI/AHOI2018]道路

在网上翻了几篇博客没看懂,自己强行开码,于是就写篇博客希望对别的“走投无路”的人有所帮助

心路历程:

普及+的题目,不知道当时自己在想什么,dp式倒是推对了,然而优化不了空间,惨痛RE,然后钦定HNOI凉凉

题意:

题目传送门

设叶子结点到根结点,会经过i条未翻修的公路,j条未翻修的铁路

则叶子结点到根结点的贡献为 c[i](a[i]+i)(b[i]+j) c [ i ] ∗ ( a [ i ] + i ) ∗ ( b [ i ] + j )

设计方案控制 i,j i , j 使 ic[i](a[i]+i)(b[i]+j) ∑ 叶 子 结 点 i c [ i ] ∗ ( a [ i ] + i ) ∗ ( b [ i ] + j ) 最小

Solution

f[u][i][j] f [ u ] [ i ] [ j ] 表示从根结点到结点u经过 i i 条未翻修公路,j条未翻修铁路

的贡献最小值

于是有针对叶子结点(乡村),先预处理出

f[u][i][j]=c[u](a[u]+i)(b[u]+j) f [ u ] [ i ] [ j ] = c [ u ] ∗ ( a [ u ] + i ) ∗ ( b [ u ] + j )

然后是城市点 u u

u>v选择修公路时,则右儿子会多经过一条未修的铁路

f[u][i][j]=f[lson][i][j]+f[rson][i][j+1] f [ u ] [ i ] [ j ] = f [ l s o n ] [ i ] [ j ] + f [ r s o n ] [ i ] [ j + 1 ]

同理,当 u>v u − > v 选择修铁路时,则左儿子会多经过一条未修的公路

f[u][i][j]=f[lson][i+1][j]+f[rson][i][j] f [ u ] [ i ] [ j ] = f [ l s o n ] [ i + 1 ] [ j ] + f [ r s o n ] [ i ] [ j ]

所以最终二者取 min m i n
f[u][i][j]=min(f[lson][i][j]+f[rson][i][j+1],f[lson][i+1][j]+f[rson][i][j]); f [ u ] [ i ] [ j ] = m i n ( f [ l s o n ] [ i ] [ j ] + f [ r s o n ] [ i ] [ j + 1 ] , f [ l s o n ] [ i + 1 ] [ j ] + f [ r s o n ] [ i ] [ j ] ) ;

然后是值得注意的细节

1. f[u][i][j] f [ u ] [ i ] [ j ] longlong l o n g l o n g ,然后中间叶子结点相乘时开1LL

2.本题卡空间, f[u][i][j] f [ u ] [ i ] [ j ] 中的一维 u u 是不能开到20000的,这显然MLE,但是我们注意到上述dp再处理过程中,是一定先把儿子处理完才处理的自己,又由于这是一颗满二叉树,所以它相当于每次处理一条链,链与链之间的不影响,所以可以定义dfn[u]表示点在链中映射的结点编号,然后只需要开到80就够了(恐怕这就是这个题目唯一有价值的地方了吧

最后是激动人心的上代码过程

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
#define ll long long
#define rg register
const int _=40100;
inline int read()
{
    rg char ch='!';rg int z=1,num=0;
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')z=-1,ch=getchar();
    while(ch<='9'&&ch>='0')num=(num<<3)+(num<<1)+ch-'0',ch=getchar();
    return z*num;
}
int n,a[_],b[_],c[_];
struct ed{int to,next;}e[_<<1];
vector<int>g[_];
int cnt,head[_];
inline void link(int u,int v){e[++cnt]=(ed){v,head[u]};head[u]=cnt;}
int sz[_],dep[_],dfn[_];
ll f[105][45][45];
void dfs(int u,int k)
{
    dfn[u]=k;
    sz[u]=1;
    for(rg int i=head[u];i;i=e[i].next)
    {
        rg int v=e[i].to;
        dep[v]=dep[u]+1;
        g[u].push_back(v);
        if(g[u][1])dfs(v,k+2);
        else dfs(v,k+1);
        sz[u]+=sz[v];
    }
    if(sz[u]==1)
        for(rg int i=0;i<=dep[u];++i)
            for(rg int j=0;j<=dep[u];++j)
                f[dfn[u]][i][j]=1ll*c[u]*(a[u]+i)*(b[u]+j);
    else
        for(int i=0;i<=dep[u];++i)
            for(int j=0;j<=dep[u];++j)
                f[dfn[u]][i][j]=min(f[dfn[g[u][0]]][i][j]+f[dfn[g[u][1]]][i][j+1],f[dfn[g[u][0]]][i+1][j]+f[dfn[g[u][1]]][i][j]);
}
int main()
{
    memset(f,63,sizeof(f));
    n=read();
    for(rg int x,y,i=1;i<n;++i)
    {
        x=read(),y=read();
        if(y<0)link(i,-y+n-1);
        else link(i,y);
        if(x<0)link(i,-x+n-1);
        else link(i,x);
    }
    for(rg int i=n;i<=2*n-1;++i)a[i]=read(),b[i]=read(),c[i]=read();
    dep[1]=0;dfs(1,0);
    printf("%lld\n",f[dfn[1]][0][0]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值