P2680 运输计划
写一个 \(M\log N+M\log (\max dis)+N\log (\max dis)\) 的题解,不会树剖的同学可以右转了。
先简化一下题面:
- 给你一颗树。
- 给你 \(M\) 个询问,每个询问涉及 \(x->lca(x,y)->y\)(\(x\) 到 \(y\) 的路径,其中路径为 \(dis_i\)) 的边权总和。
- 让你清空一条树上的权值,使 \(M\) 个询问的 \(dis\) 最大值最小。
首先我们要预处理出 \(dis\) (每一个询问在路上的距离)。我们可以直接求出 \(lca(x,y)\),且直接用树上前缀和。因为数据很可能使一条链,所以我们用树剖求 \(lca\) (这样子树剖是 \(O(1)\) 的)。但是因为这里是不是点,是边,我们照例“以点代边”。所以路径就变为了 \(x->lca(x,y)_{son}->y\) (就是不包含 \(lca\) 本身)。
但是我们还要预处理点权啊! 所以我们先 \(Dfs_1\),求出所有的 \(dep\)。然后我们就可以枚举每一条边,每一条边 (链式前向星) 分别记录 \(from_i\) 和 \(reach_i\) ,然后看这两个端点谁的 \(dep\) 深,谁就继承着条边的 \(val_i\)。树上前缀和就可以直接在 \(Dfs_2\) 里面搞啦。
考虑到 \(dis\) 最大值最小,我们用二分答案。怎么二分? 我们设 \(l,r\) 为左右边界 (初始值: \(l=0,r=\max dis\)), \(mid=\frac{l+r}{2}\),\(mid\) 也就是我们设想的答案。如果答案合法,\(r\) 和 \(ans=mid\) (找更小 \ 优)的答案。否则 \(l=mid+1\),找合法答案。
现在就只剩怎么判断一个答案合法了。我们跑一遍循环,把所有 \(dis_i>mid\) 的询问找下来 (因为这些 \(dis\) 都是大于 \(mid\) 的那 \(mid\) 不能是答案啊),然后把 \(l,r\) (就是询问给的两个端点,注意不是链式前向星) 的树上路径全部 \(+1\)。最后跑完了,我们看每一条树边,如果它们触碰到了每一条边且权值大于 \(\max dis-mid\) 的话 (也就是这一条边被加了 \(num\) 次 (\(num\) 为 \(dis\) 超过 \(mid\) 的个数),且它可以使所有边都小于 \(mid\)),合法。如果全部都没有合法,就是不合法啦。
如何让树上路径 \(+1\) 啊? 因为我们已经用到了树剖啦,所以我还是滚过去用树上差分啦。不会树上差分的可以右转日报,这里说一下,因为是“以点代边”,所以 我们只需要给 \(lca(x,y)-2\) 就可以了。
第 \(13\) 个点完全没有卡到我 \(Pascal\) 的代码啦。
#13
AC
1242ms/41384K
// luogu-judger-enable-o2
Uses math;
var
cnt,size,dep,top,sum,val,father,son:array[-1..310000] of int64;
// 分别为链式前向星的 head,子树大小,深度,链顶,差分/前缀和(两个用处哦),点权,父亲,重儿子
next,reach,value,from:array[-1..650000] of longint;
// 都是链式前向星的, next,from(左端点),reach(右端点),value(边权/树边)
lca,dis,ll,rr:array[-1..650000] of int64;
// 询问的 lca,dis,记录每一个询问的左端点 ll,右端点 rr
j,i,n,m,l,r,ans,mid,tmp,tot,root,maxdis:longint;
k,modn:int64;
procedure swap(var a,b:longint);var t:longint; begin t:=a; a:=b; b:=t; end;
procedure add(l,r,sum:longint); // 加边
begin
inc(tot);
value[tot]:=sum;
from[tot]:=l;
reach[tot]:=r;
next[tot]:=cnt[l];
cnt[l]:=tot;
end;
procedure Dfs_1(x:longint);
// Dfs1 预处理 size,dep,father
var i:longint;
begin
size[x]:=1; i:=cnt[x]; size[0]:=-maxlongint div 843;
while i<>-1 do
begin
if dep[reach[i]]=0 then
begin
dep[reach[i]]:=dep[x]+1;
father[reach[i]]:=x;
Dfs_1(reach[i]); inc(size[x],size[reach[i]]);
if size[reach[i]]>size[son[x]] then son[x]:=reach[i];
end;
i:=next[i];
end;
end;
procedure Dfs_2(x,centre:longint);
// Dfs2 预处理 sum(从根到叶子节点:树上前缀和),son 重儿子,top 链顶
var i:longint;
begin
sum[x]:=sum[father[x]]+val[x]; top[x]:=centre;
if son[x]=0 then exit; Dfs_2(son[x],centre);
i:=cnt[x];
while i<>-1 do
begin
if (reach[i]<>father[x])and(reach[i]<>son[x]) then Dfs_2(reach[i],reach[i]);
i:=next[i];
end;
end;
procedure Dfs_3(x:longint);
// 这个是用来搞树上差分的前缀和的 (从叶子到根哦)
var i:longint;
begin
i:=cnt[x];
while i<>-1 do
begin
if (reach[i]<>father[x]) then
begin
Dfs_3(reach[i]);
inc(sum[x],sum[reach[i]]);
end;
i:=next[i];
end;
end;
function Refer(x,y:longint):int64; // 树剖 LCA
begin
while top[x]<>top[y] do
begin
if dep[top[x]]<dep[top[y]] then y:=father[top[y]] else x:=father[top[x]];
end;
if dep[x]<dep[y] then exit(x) else exit(y);
end;
function Check(mid:longint):boolean;
var i,num:longint;
begin
fillchar(sum,sizeof(sum),0); maxdis:=-maxlongint div 843; num:=0;
for i:=1 to m do // 扫每一个询问
if dis[i]>mid then
begin
inc(num);
inc(sum[ll[i]]); // 树上差分,给每一个路径 +1
inc(sum[rr[i]]);
dec(sum[lca[i]],2);
maxdis:=max(maxdis,dis[i]-mid); // 最大的距离
end;
Dfs_3(root);
for i:=1 to n do if (sum[i]=num)and(val[i]>=maxdis) then exit(True); // 找合法
exit(False);
end;
procedure Ready; // 预处理
begin
filldword(cnt,sizeof(cnt) div 4,maxlongint*2+1); // 赋值 -1
read(n,m); root:=1;
for i:=1 to n-1 do
begin
read(l,r,k);
add(l,r,k);
add(r,l,k);
end;
dep[root]:=1; father[root]:=1; Dfs_1(root);
for i:=1 to tot do if dep[from[i]]>dep[reach[i]] then val[from[i]]:=value[i] else val[reach[i]]:=value[i]; // 赋予点权
Dfs_2(root,root);
for i:=1 to m do // 预处理信息
begin
read(l,r);
lca[i]:=Refer(l,r);
dis[i]:=(sum[l]-sum[lca[i]])+(sum[r]-sum[lca[i]]);
ll[i]:=l; rr[i]:=r;
maxdis:=max(maxdis,dis[i]);
end;
end;
begin
Ready; l:=0; r:=maxdis;
while l<r do // 如上述
begin
mid:=(l+r) >> 1;
if Check(mid) then
begin r:=mid; ans:=mid; end
else l:=mid+1;
end;
writeln(ans);
end.
复杂度分析:
- \(Dfs_1\) 和 \(Dfs_2\) 一共 \(O(N)\)。
- 预处理 \(lca\),一次 \(O(\log N)\),总共 \(O(M \log N)\)。
- 预处理 \(dis\),一次 \(O(1)\),总共 \(O(M)\)。
- 二分,一次 \(Check\) 是 \(O(M+N+N)\),总共是 \((M+N+N) \log \max dis\)。
就是 \(O(N \log \max dis)\) 啦。