Codeforces Round 892 (Div. 2) F. Teleportation in Byteland(多源dijkstra+lca&树倍增+分类讨论)

文章讲述了如何利用树结构和加油站分布解决一个关于最短时间查询的问题,通过预处理加油站到各点的距离、加油前后的时间以及离线处理查询,使用dijkstra和倍增算法来优化计算过程,保证在给定时间和空间限制下求解效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目

给定一个n(n<=1e5)个点的树,边权w(1<=w<=1e6)

给定一个长为n的01串,为1的代表是加油站,

在加油站,每加一次油的时间是T(1<=T<=1e6),每个点可以无数次加油

初始速度v是1,每加一次油,当前速度v就会乘2,而通过一条边的时间是\left \lceil \frac{w}{v} \right \rceil

q(q<=1e5)次询问,每次给出u、v,询问从u到v的最短时间

实际t(t<=1e4)组样例,但sumn和sumq不超过1e5

思路来源

官方题解

题解

对于每组询问,先算一个不加油的时间,然后考虑加油的情况,

由于w是1e6,所以最多加油20次,通过一条边的时间就可以控制在1s

所以可以枚举加油次数k,加油次数固定后,考虑这个从u到v的过程

1. v=1,从u走到链上点x

2. v=1,从x走到最近加油站y,加油k次;v=2^k,从y回到x

3. v=2^k,从x去v

可能会考虑说,第二步中,如果x对应的y在x和v之间,是不需要从y回到x的,

不过,此时链上x处的答案,会不如y处的答案,所以还是不影响最优解的

由于边通过的时间,有向上取整,现算比较困难

所以,可以建k棵树出来,边权是加油后原来的边权除以速度向上取整的值

而1-3这三步中,2是可以预处理出来的,

可以用所有加油站跑多源dijkstra,得到每个点到最近加油站的加油前去的时间&加油后去的时间

相当于对于u->v链上的每个点x,都得到一个对应的时间,

而第一步、第三步的时间中,关于x的部分也可以拆出来维护到x的项上

此外,u到lca前半段,lca到v后半段,两段的式子会有不同,所以需要分别维护mn和mn2数组

具体来说,按照官方题解的式子,需要写作:

如果在前半段(官方题解typo)是第一个式子,否则是第二个式子,而v1就是刚才提到的x

由于空间问题,所以需要将询问离线后,

再枚举所有k处理所有答案后,再回答

复杂度大致为O((n+q)*logw*logn)

心得

思路比较朴素,但粘了若干个板子,细节较多,比较难调,所以比较难赛中AC

int lc=lca(u,v),d1=dep[u]-dep[lc]+1,d2=dep[v]-dep[lc]+1;

这里有个点需要注意,d1和d2分别等于u到lca的距离+1,v到lca的距离+1

较实际距离加了1,是因为:

lca的倍增f[x][0]是x的父亲的值,

而维护的最小值的倍增mn[x][0]是x自己的值

相当于前者是开区间,后者是闭区间,所以用到后者时,需要加1

代码

#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define per(i,a,b) for(int i=(a);i>=(b);--i)
#define sci(n) scanf("%d",&n)
#define pb push_back
#define SZ(a) (int)(a.size())
#define fi first
#define se second
typedef long long ll;
typedef pair<int,int> P;
typedef pair<ll,int> PL;
const int N=1e5+10,M=20,L=17;
const ll INF=0x3f3f3f3f3f3f3f3fll;
vector<P>E[N];
int n,T,f[N][L+1],dep[N];
ll dis[M+1][N],mn[N][L+1],mn2[N][L+1],sum[M+1][N],ans[N];
P ask[N];
bool vis[N];
char s[N];
void init2(ll a[][L+1]){// 树上倍增
    rep(i,1,L){
        rep(j,1,n){
            a[j][i]=min(a[j][i-1],a[f[j][i-1]][i-1]);
        }
	}
}
void dfs(int u,int fa){
	for(auto &x:E[u]){
		int v=x.fi,w=x.se;
		if(v==fa)continue;
		dep[v]=dep[u]+1;
        rep(i,0,M){
            ll bs=1<<i;
            sum[i][v]=sum[i][u]+(w+bs-1)/bs;
        }
		f[v][0]=u;
        rep(i,1,L){
            f[v][i]=f[f[v][i-1]][i-1];
        }
		dfs(v,u);
	}
}
void dijkstra(int k){
    priority_queue<PL,vector<PL>,greater<PL>>pq;
    rep(j,0,k){
        ll bs=1<<j;
        rep(i,1,n){
            dis[j][i]=INF;
            vis[i]=0;
            if(s[i]=='1')pq.push(PL(dis[j][i]=0,i));
        }
        while(!pq.empty()){
            int u=pq.top().second;
            pq.pop();
            if(vis[u])continue;
            vis[u]=1;
            for(auto &x:E[u]){
                int v=x.fi;
                ll w=(x.se+bs-1)/bs+x.se;
                if(dis[j][v]>dis[j][u]+w){
                    dis[j][v]=dis[j][u]+w;
                    pq.push(PL(dis[j][v],v));
                }
            }
        }
    }
}
inline int lca(int x,int y){
	if(dep[x]<dep[y])swap(x,y);
	int d=dep[x]-dep[y];
	per(i,L,0)if(d>>i&1)x=f[x][i];
	if(x==y)return x;
	per(i,L,0)if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];
	return f[x][0];
}
ll amn(int x,int d,ll a[][L+1],ll s){
    ll ans=INF;
	per(i,L,0){
        if(d>>i&1){
            ans=min(ans,s+a[x][i]);
            x=f[x][i];
        }
    }
	return ans;
}
ll sol(int u,int v,int k){
    if(u==v)return 0;
    int lc=lca(u,v),d1=dep[u]-dep[lc]+1,d2=dep[v]-dep[lc]+1;
    ll s1=sum[0][u]+sum[k][v]-2*sum[k][lc];
    ll s2=sum[0][u]+sum[k][v]-2*sum[0][lc];
    ll ans=min(amn(u,d1,mn,s1),amn(v,d2,mn2,s2));
    return ans;
}
void read(){
    sci(n);sci(T);
    rep(i,1,n)E[i].clear();
    int u,v,w;
    rep(i,1,n-1){
        sci(u),sci(v),sci(w);
        E[u].push_back(P(v,w));
        E[v].push_back(P(u,w));
    }
    scanf("%s",s+1);
    dfs(1,0);
    dijkstra(M);
}
void sol(){
    int q,u,v;
    sci(q);
    rep(i,1,q){
        sci(u),sci(v);
        ask[i]={u,v};
        ans[i]=sum[0][u]+sum[0][v]-2*sum[0][lca(u,v)];
    }
    rep(k,1,M){
        rep(j,1,n){
            mn[j][0]=dis[k][j]-sum[0][j]+sum[k][j];
            mn2[j][0]=dis[k][j]+sum[0][j]-sum[k][j];
        }
        init2(mn);init2(mn2);
        rep(i,1,q){
            auto [u,v]=ask[i];
            ans[i]=min(ans[i],1ll*k*T+sol(u,v,k));
        }
    }
    rep(i,1,q){
        printf("%lld\n",ans[i]);
    }
}
int main(){
    int t;
    sci(t);
    while(t--){
        read();
        sol();
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小衣同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值