[BZOJ2286] [Sdoi2011]消耗战

本文介绍了一道关于树形DP的算法题,通过构造虚树并利用ST表预处理来优化算法效率,实现对特定关键点进行切断操作以最小化总消耗。

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

传送门

http://www.lydsy.com/JudgeOnline/problem.php?id=2286

题目大意

给定一棵树,树上有边权,切断一条边消耗边权大小的能量
每次给定一些关键点,使这些关键点都不能与1联通,询问最小代价

题解

树形DP
dp[i]:使i
dp[i]=min{dp[son[i]],w[i,son[i]]}son[i]/w[i,son[i]]son[i]
复杂度 O(NM) 超时
我们发现其实不用每次都对整棵树做一遍 DP ,每次只留下关键点和汇集关键点的点( lca )即可
(下面的父子关系是省略无关点后的新树)
dp[i]=min{dp[son[i]],w[i,son[i]]}son[i]/w[i,son[i]]son[i]
其中 w[i,son[i]] 是原树中两点见的最小边权
可以用ST表预处理出来做到每次 O(logN)
(这一种抛去无关点建立的新树通常叫做虚树)
如何建立,看下面一段

接下来所说的”树”均指虚树,原来那棵树叫做”原树”.
构建过程如下:
按照原树的dfs序号(记为dfn)递增顺序遍历选择的节点. 每次遍历节点都把这个节点插到树上.
首先虚树一定要有一个根. 随便扯一个不会成为询问点的点作根.
维护一个栈,它表示在我们已经(用之前的那些点)构建完毕的虚树上,以最后一个插入的点为端点的DFS链.
设最后插入的点为p(就是栈顶的点),当前遍历到的点为x.我们想把x插入到我们已经构建的树上去.
求出lca(p,x),记为lca.有两种情况:
  1.p和x分立在lca的两棵子树下.
  2.lca是p.
  (为什么lca不能是x?
   因为如果lca是x,说明dfn(lca)=dfn(x)

const
    maxn=250000;
var
    w:array[0..3*maxn,1..3]of longint;
    dp:array[0..maxn]of int64;
    pos,dep,t,s,v,p:array[0..maxn]of longint;
    st:array[0..maxn,0..20]of longint;
    dis:array[0..maxn,0..20]of int64;
    i,j,k:longint;
    n,m,len,a,b,c,tt:longint;
function min(a,b:int64):int64;
begin if a>b then exit(b) else exit(a); end;

procedure init(a,b,c:longint);
begin
    w[len,1]:=b; w[len,2]:=c;
    if w[a,3]=0 then w[a,3]:=len else w[w[a,1],3]:=len;
    w[a,1]:=len; inc(len);
end;

procedure sort(l,r:longint);
var i,j,a,b:longint;
begin
    i:=l;j:=r;a:=pos[s[(l+r)div 2]];
    repeat
        while (pos[s[i]]<a) do inc(i);
        while (pos[s[j]]>a) do dec(j);
        if not(i>j) then begin b:=s[i]; s[i]:=s[j]; s[j]:=b; inc(i); dec(j); end;
    until i>j;
    if l<j then sort(l,j);
    if i<r then sort(i,r);
end;

procedure dfs(a:longint);
var tt:longint;
begin
    tt:=w[a,3]; inc(len); pos[a]:=len;
    while tt<>0 do
        begin
            if (w[tt,1]<>st[a,0])
            then
                begin
                    dep[w[tt,1]]:=dep[a]+1;
                    st[w[tt,1],0]:=a;
                    dis[w[tt,1],0]:=w[tt,2];
                    dfs(w[tt,1]);
                end;
            tt:=w[tt,3];
        end;
end;

procedure prepare;
var i:longint;
begin
    len:=0; dep[1]:=1; dfs(1);
    for i:=1 to 20 do
        for j:=1 to n do
            st[j,i]:=st[st[j,i-1],i-1];
    for i:=1 to 20 do
        for j:=1 to n do
            dis[j,i]:=min(dis[j,i-1],dis[st[j,i-1],i-1]);
end;

function lca(a,b:longint):longint;
var i:longint;
begin
    if dep[a]<dep[b] then begin i:=a; a:=b; b:=i; end;
    for i:=20 downto 0 do
        if dep[st[a,i]]>=dep[b]
        then a:=st[a,i];
    if a=b then exit(a);
    for i:=20 downto 0 do
        if st[a,i]<>st[b,i]
        then begin a:=st[a,i]; b:=st[b,i]; end;
    exit(st[a,0]);
end;

function distance(a,b:longint):int64;
var ans:int64; i:longint;
begin
    if dep[a]<dep[b] then begin i:=a; a:=b; b:=i; end;
    ans:=maxlongint;
    for i:=20 downto 0 do
        if dep[st[a,i]]>=dep[b]
        then begin ans:=min(ans,dis[a,i]); a:=st[a,i]; end;
    exit(ans);
end;

begin
    readln(n); len:=n+1;
    for i:=1 to n-1 do
        begin
            readln(a,b,c);
            init(a,b,c); init(b,a,c);
        end;
    prepare;
    readln(m);
    for i:=1 to m do
        begin
            read(a);
            for j:=1 to a do
                begin read(s[j]); v[s[j]]:=1; end;
            sort(1,a);
            t[1]:=1; t[0]:=1; p[0]:=1; p[1]:=1; len:=n+1;
            for j:=1 to a do
                begin
                    tt:=lca(t[t[0]],s[j]);
                    if tt=t[t[0]]
                    then begin inc(t[0]); t[t[0]]:=s[j]; inc(p[0]); p[p[0]]:=s[j]; end
                    else
                        begin
                            while dep[t[t[0]-1]]>dep[tt] do
                                begin
                                    if v[t[t[0]]]=1
                                    then inc(dp[t[t[0]-1]],distance(t[t[0]],t[t[0]-1]))
                                    else inc(dp[t[t[0]-1]],min(distance(t[t[0]],t[t[0]-1]),dp[t[t[0]]]));
                                    dp[t[t[0]]]:=0; dec(t[0]);
                                end;
                            if v[t[t[0]]]=1
                            then inc(dp[tt],distance(tt,t[t[0]]))
                            else inc(dp[tt],min(distance(tt,t[t[0]]),dp[t[t[0]]]));
                            dp[t[t[0]]]:=0; dec(t[0]);
                            if t[t[0]]<>tt then begin inc(t[0]); t[t[0]]:=tt; inc(p[0]); p[p[0]]:=s[j]; end;
                            inc(t[0]); t[t[0]]:=s[j]; inc(p[0]); p[p[0]]:=s[j];
                        end;
                end;
            while t[0]>1 do
                begin
                    if v[t[t[0]]]=1
                    then inc(dp[t[t[0]-1]],distance(t[t[0]],t[t[0]-1]))
                    else begin inc(dp[t[t[0]-1]],min(distance(t[t[0]],t[t[0]-1]),dp[t[t[0]]])); dp[t[t[0]]]:=0; end;
                    dec(t[0]);  
                end;
            writeln(dp[1]); 
            for j:=1 to p[0] do
                dp[p[j]]:=0;
            for j:=1 to a do
                v[s[j]]:=0;
        end;
end.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值