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

题目

给定一个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数组

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

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

如果在前半段(官方题解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
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 P;
typedef pair PL;
const int N=1e5+10,M=20,L=17;
const ll INF=0x3f3f3f3f3f3f3f3fll;
vector

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<,greater>pq; rep(j,0,k){ ll bs=1<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]>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; }

你可能感兴趣的:(#,#,倍增,lca,多源dijkstra)