【hiho1035】自驾旅行III

题目大意:给定一棵 N 个节点的有根树,1 号节点为根节点,树边有两个权值,分别为走路的代价和开车的代价。有一个旅行者开车要从根节点出发,必须遍历给定点集,可以在任何位置停止旅行,有车时可以选择开车或步行,没车只能跑路,求最小代价。

题解:这是我做过最恶心的树形dp QAQ
和 apple tree 这道题很相似,只不过这次是多了车这个东西。因此,在设计状态的时候要考虑到人和车的关系,dp[u][0] 表示人走到以 u 为根的子树中必须返回的最小权值,dp[u][1] 表示人下去但是不一定上来的最小代价,dp[u][2] 表示人和车都可以下去,人和车都必须上来的最小代价,dp[u][3] 表示人和车可以下去,人上来车不一定上来的最小代价,dp[u][4] 表示人和车可以下去,但是人和车都不一定上来的最小代价。

dp[u][0-3] 的转移方程比较好想,对于最后一种情况来说,分为走最后一棵子树的时候有没有车,如果没有车就意味着在之前遍历的某棵子树中只有人回来了,因此需要考虑两棵子树对答案的贡献。可是发现,两棵子树显然不能是同一棵,因此需要记录下最优解和次优解,以及取得最优解的是哪颗子树,这样才能合并两棵子树的贡献。

代码如下

#include <bits/stdc++.h>
using namespace std;
const int maxn=1e6+10;
typedef long long LL;

int n,m,key[maxn];
LL dp[maxn][5];
struct node{int nxt,to;LL w0,w1;}e[maxn<<1]; // w0 -> walk   w1 -> car
int tot=1,head[maxn];
inline void add_edge(int from,int to,LL w0,LL w1){
    e[++tot]=node{head[from],to,w0,w1},head[from]=tot;
}

void dfs(int u,int fa){
    LL d1=0,d3=0,d4=0;
    LL fi1=1e18,se1=1e18,fi2=1e18,se2=1e18; // fi1 -> dp[v][3]+w0+w1  fi2 -> dp[v][1]+w0
    int x=0,y=0;
    for(int i=head[u];i;i=e[i].nxt){
        int v=e[i].to; LL w0=e[i].w0,w1=e[i].w1;
        if(v==fa)continue;
        dfs(v,u);
        key[u]+=key[v];
        if(key[v]){
            LL t=min(dp[v][0]+2*w0,dp[v][2]+2*w1);
            
            dp[u][0]+=dp[v][0]+2*w0;
            dp[u][2]+=t;
            d1=min(d1,dp[v][1]-dp[v][0]-w0);
            d3=min(d3,dp[v][3]+w0+w1-t);
            d4=min(d4,min(dp[v][4]+w1,dp[v][1]+w0)-t);
            
            LL ret1=dp[v][3]+w0+w1-t;
            if(ret1<fi1)se1=fi1,fi1=ret1,x=v;
            else se1=min(se1,ret1);
            
            LL ret2=dp[v][1]+w0-t;
            if(ret2<fi2)se2=fi2,fi2=ret2,y=v;
            else se2=min(se2,ret2);
        }
    }
    if(x&&y){
        if(x!=y)d4=min(d4,fi1+fi2);
        else d4=min(d4,min(fi1+se2,fi2+se1));
    }
    dp[u][1]=dp[u][0]+d1;
    dp[u][3]=dp[u][2]+d3;
    dp[u][4]=dp[u][2]+d4;
}

void read_and_parse(){  
    scanf("%d",&n);
    for(int i=1;i<n;i++){
        int x,y,w0,w1;
        scanf("%d%d%d%d",&x,&y,&w0,&w1);
        add_edge(x,y,w0,w1),add_edge(y,x,w0,w1);
    }
    scanf("%d",&m);
    for(int i=1;i<=m;i++){
        int x;scanf("%d",&x);
        key[x]++;
    }
}
void solve(){
    dfs(1,0);
    printf("%lld\n",dp[1][4]);
}
int main(){
    read_and_parse();
    solve();
    return 0;
} 

转载于:https://www.cnblogs.com/wzj-xhjbk/p/10998425.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值