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)\) 啦。

posted @ 2018-10-24 13:48  _ARFA  阅读(139)  评论(0编辑  收藏  举报