zoukankan      html  css  js  c++  java
  • 【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
    ",dp[1][4]);
    }
    int main(){
    	read_and_parse();
    	solve();
    	return 0;
    } 
    
  • 相关阅读:
    组件封装小试:使用Vue CLI3基于Element-ui进行组件封装
    <四>docker中的mysql和mongodb挂载
    <五>.netcore webapi连接部署在docker中的mysql
    <三>docker安装mysql
    <二>docker 安装mongodb
    <一>docker基础及centos安装docker
    <十一>将identityserver4的一些权限配置持久化到数据库
    <十>使用profileserver获取用户数据
    <九>集成ASP.NETCore Identity 作为授权账户持久到数据库
    <一>初试Identity
  • 原文地址:https://www.cnblogs.com/wzj-xhjbk/p/10998425.html
Copyright © 2011-2022 走看看