花花的聚会 [DP][倍增]

这里写图片描述

这里写图片描述

题意描述

花花住在H 国。H 国有n 个城市,其中1 号城市为其首都。城市间有n-1 条单向道路。从任意一个城市出发,都可以沿着这些单向道路一路走到首都。事实上,从任何一个城市走到首都的路径是唯一的。过路并不是免费的。想要通过某一条道路,你必须使用一次过路券。H 国一共有m 种过路券,每张过路券以三个整数表示:v k w,你可以在城市v 以价格w 买到一张过路券。这张券可以使用k 次。这意味着,拿着这张券通过了k 条道路之后,这张券就不能再使用了。请注意你同一时间最多只能拥有最多一张过路券。但你可以随时撕掉手中已有的过路券,并且在所在的城市再买一张。花花家在首都。他有q 位朋友,他希望把这些朋友们都邀请到他家做客。所以他想要知道每位朋友要花多少路费。他的朋友们都很聪明,永远都会选择一条花费最少的方式到达首都。花花需要准备晚餐去了,所以他没有时间亲自计算出朋友们将要花费的路费。你可以帮帮他么?
输入格式
输入的第一行包含两个空格隔开的整数n 和m,表示H 国的城市数量和过路券的种数。之后的n-1 行各自包含两个数ai 和bi,代表城市ai 到城市bi 间有一条单向道路。之后的m 行每行包括三个整数vi; ki 和wi,表示一种过路券。下一行包含一个整数q,表示花花朋友的数量。之后的q 行各自包含一个整数,表示花花朋友的所在城市。
输出格式
输出共q 行,每一行代表一位朋友的路费。
样例输入
7 7
3 1
2 1
7 6
6 3
5 3
4 3
7 2 3
7 1 1
2 3 5
3 6 2
4 2 4
5 3 10
6 1 20
3
5
6
7
样例输出
10
22
5
样例解释
对于第一位朋友,他在5 号城市只能购买一种过路券,花费10 元并且可以使用3 次。这足够他走到首都,因此总花费是10 元。对于第二位朋友,他在6 号城市只能购买20 元的过路券,并且只能使用一次。之后,他可以在3 号城市购买2 元,可以使用3 次的过路券走到首都。总花费是22 元。对于第三位朋友,他在7 号城市可以购买两种过路券。他可以花3 元买一张可以使用2次的券,然后在3 号城市再买一张2 元,可以使用3 次的券,走到首都。总花费是5 元,而且其他的购买方式不会比这种更省钱。
数据规模与约定
• 对于40% 的数据:n,m,q<=10,wi<=10;
• 另有20% 的数据:n,m,q<=500,wi<=100;
• 另有20% 的数据:n,m,q<=5000,wi<=1000;
• 对于100% 的数据:n,m,q<= 105 ;wi<=10000; 1<=vi,ki<=n。

题解

只是问能不能帮助他的话,回答否不就行了吗?——by xwl
。。。

真的不知道写链剖、lct的脑袋是否有毒。。

不难发现,我们可以用动态规划的思想来解决这个问题。记f[u] 为u 跳到根的最少代价。则不难得出转移方程:f[u] = min{f[v]+wi},其中v 是u 的祖先,且存在一种车票ui,wi,ki并满足dep[u]-dep[v]<=ki。这个DP方程的边界条件为f[root] = 0。这样对于每种车票,我们可以暴力枚举所有合法的v,从而求得答案。事实上我们只需在较快的时间内求得树上某一段区间的min{f[u]},即可较好地解决这个问题。而维护树上区间最值有树链剖分、动态树等许多数据结构能够胜任。在这里我们考虑用倍增的思想,维护u往上跳2i 步到的祖先v和u到v这一段f 值的最小值,这样我们对于每一个节点u 可以在O (logn) 的时间内求得min{f[v]}。
时间复杂度为 O(nlogn)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
const int N = 142857;
const long long INF = 1e18;
inline void read(int &res){
    static char ch;register int flag=1;
    while((ch=getchar())<'0'||ch>'9')if(ch=='-')flag=-1;res=ch-48;
    while((ch=getchar())>='0'&&ch<='9')res=res*10+ch-48;res*=flag;
}
int v,u,w,n,m,q;
long long fa[N][20],val[N][20],dp[N];
vector<int> G[N],V[N],E[N];
void dfs(int x,int f){//尽管不想写倍增。。。写个链剖+线段树血亏啊!! 
    fa[x][0]=f,val[x][0]=dp[f];
    for(register int i=1;i<18;i++){
        fa[x][i]=fa[fa[x][i-1]][i-1];
        val[x][i]=min(val[x][i-1],val[fa[x][i-1]][i-1]);
    }
    dp[x]=x==1?0:INF;
    for(register int i=0;i<E[x].size();i++){
        long long tmp=INF,y=x,k=E[x][i],p=0;
        while(k){
            if(k&1){
                tmp=min(tmp,val[y][p]);
                y=fa[y][p];
            } 
            p++;k>>=1;
        }
        dp[x]=min(dp[x],tmp+V[x][i]);
    }
    for(register int y,i=0;i<G[x].size();i++)
        if(y=G[x][i],y!=f)dfs(y,x);
}
int main(){
    freopen("party.in","r",stdin);
    freopen("party.out","w",stdout); 
    read(n),read(m);
    for(register int i=1;i<n;i++){
        read(u),read(v);
        G[u].push_back(v),G[v].push_back(u);
    }
    for(register int i=1;i<=m;i++){
        read(u),read(v),read(w);
        E[u].push_back(v),V[u].push_back(w);
    }
    dfs(1,1),read(q);
    for(register int i=1;i<=q;i++)
        read(u),cout<<dp[u]<<endl;
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值