Truck Delivery - Google Kickstart Round B 2021第四题
Charles is a truck driver in the city of Googleland. Googleland is built in form of a tree with N nodes where each node represents a city and each edge represents a road between two cities. The cities are numbered 1 to N. The capital of Googleland is city 1. Each day Charles picks up a load of weight W in city C and wants to deliver it to city 1 using the simple path (which is unique) between the cities. Each road i has a toll which charges amount Ai if the weight of the load is greater than or equal to a load-limit Li.
Charles works for Q days, where for each day Charles will be given the starting city C and weight of the load W. For each day find the greatest common divisor of all the toll charges that Charles pays for that day. If Charles did not have to pay in any of the tolls the answer is 0.
题目大意:
Googleland是一个树形图,一共有N( 1 ≤ N ≤ 2 × 1 0 4 1\leq N\leq2\times10^4 1≤N≤2×104)座城市。 城市中的道路都是双向边,道路 i i i会对重量至少为 L [ i ] L[i] L[i](每条道路不同)的车辆收取 a [ i ] a[i] a[i]的过路费( 1 ≤ a [ i ] ≤ 1 0 18 1\leq a[i]\leq10^{18} 1≤a[i]≤1018)。Charles每天要从城市C把重量为W( 1 ≤ W , L [ i ] ≤ 2 × 1 0 5 1\leq W,L[i]\leq2\times10^5 1≤W,L[i]≤2×105)的货物以简单路径运输到城市1,求每天Charles交去的过路费的最大公约数(若不用交费则答案为0)。
思路分析:
题目提示比较明显是一道线断树题目。注意到每条道路的载重不同,我们可以以载重为轴建立线段树,然后合并时取最大公约数即可。我们以深搜序遍历每个顶点,每到一个顶点就更新线段树当前位置的值为
a
[
i
]
a[i]
a[i],删除时再改回0(这样对最大公约数计算无影响),询问时只需要区间查询载重为1到w的点值的最大公约数即可。我们只在每一个顶点处更新2次,对于每一天做一次询问,每次询问或更新要乘上一个求最大公约数的复杂度,所以算法复杂度为
O
(
(
N
+
Q
)
l
o
g
(
m
a
x
(
l
[
i
]
)
l
o
g
(
m
a
x
(
a
[
i
]
)
)
O((N+Q)log(max(l[i])log(max(a[i]))
O((N+Q)log(max(l[i])log(max(a[i])),实际上由于常数很小加上大多数运算公约数有一边都是0,过时限是绰绰有余的。
具体见代码:
代码样例(已作防抄袭处理):
#include <bits/stdc++.h>
using namespace std;
const int N=5e4+5,M=2e5+5,inf=1e9+7;
#define ull unsigned long long
#define pii pair<int,int>
int n,m,Q,T;
int h[N],to[N<<1],nex[N<<1],lo[N<<1],idx;
ull d[N<<1],ans[M];
bool vis[N];
struct seg{
int l,r;
ull v=0;
}tr[M<<2];
vector<pii> q[N];
inline ull gcd(ull a,ull b)//辗转相除求最大公约数
{
if(a<b)swap(a,b);
return b==0?a:gcd(b,a%b);
}
inline void add(int x,int y,int a,int b)//加边注意long long
{
to[idx]=y,nex[idx]=h[x],lo[idx]=a,d[idx]=b,h[x]=idx++;
}
inline void build(int u,int l,int r){
tr[u]={l,r};
if(l==r)return;
int mid=l+r>>1;
build(u<<1,l,mid),build(u<<1|1,mid+1,r);
}
inline void pushup(int u){
tr[u].v=gcd(tr[u<<1].v,tr[u<<1|1].v);
}
inline ull query(int x,int y,int u){
if(tr[u].l>=x&&tr[u].r<=y)return tr[u].v;
int mid=tr[u].l+tr[u].r>>1;
ull ls=0,rs=0;
if(mid>=x)ls=query(x,y,u<<1);
if(mid<y)rs=query(x,y,u<<1|1);
if(ls&&rs)return gcd(ls,rs);//避免有一个是0返回0
return ls+rs;
}
inline void update(int p,int u,ull v){
if(tr[u].l==tr[u].r){
tr[u].v=v;
return;
}
int mid=tr[u].l+tr[u].r>>1;
if(mid>=p)update(p,u<<1,v);
else update(p,u<<1|1,v);
pushup(u);
}
inline void dfs(int p){
vis[p]=true;
for(auto x:q[p]){
int id=x.first,w=x.second;
ans[id]=query(1,w,1);
}
for(int i=h[p];~i;i=nex[i]){
int j=to[i];
if(vis[j])continue;
update(lo[i],1,d[i]);//更新
dfs(j);
update(lo[i],1,0);//回复状态
}
}
inline void solve(int ca){
scanf("%d%d",&n,&Q);
memset(h,-1,sizeof h);
memset(vis,0,sizeof vis);//初始化
idx=0,m=0;
for(int i=1;i<n;i++){
int x,y,a;
ull b;
scanf("%d%d%d%lld",&x,&y,&a,&b);
add(x,y,a,b);
add(y,x,a,b);
m=max(m,a);
}
for(int i=1;i<=Q;i++){
int c,w;
scanf("%d%d",&c,&w);
q[c].push_back(make_pair(i,w));
}
dfs(1);
printf("Case #%d: ",ca);
for(int i=1;i<=Q;i++)printf("%lld ",ans[i]);
puts("");
}
int main(){
scanf("%d",&T);
build(1,1,2e5);
for(int i=1;i<=T;i++){
solve(i);
}
}
本题作为本轮压轴题,思路还是比较明显的,代码虽然看起来不算短,但其实都是套的。不过,kickstart本来也算很水的比赛,本题已经算是有一定难度的了。