【GDSOI2017第三轮模拟】Travel Plan

本文介绍了一种针对特定范围的子树DP算法实现方案,通过分析较小的value值特性,利用DFS序对前缀和后缀进行DP操作,并提供了一个具体的代码示例。

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

Description

这里写图片描述

Sample Input

4
1 2
2 3
2 4
3 6
2 4
4 7
4 9
2
3 8
4 8

Sample Output

3
4

这里写图片描述

题解

首先观察一下这个范围,发现cost很大,但是value很小,所以我们考虑围绕value来dp
容易发现每一次ban掉的是一颗子树,也就是dfs序中的一段,所以我们可以考虑对前缀做一次dp,对后缀做一次dp,然后再合并
设f[i][j]表示我先在做到第i个点,获得j贡献所需的最小的代价,这个转移显然
现在考虑怎么合并
对于一个点x,它ban掉的就是dfs序中的t—-t+size[t]-1这些点,所以我们可以直接用两个指针来把前后缀合并一下
但是这样做理论时间复杂度很高,所以要注意一些优化

贴代码

const mc=4000000000;
var
    a,b:array[0..1001,0..50000]of longword;
    new,size,co,va,s1,s2:array[0..1005]of longint;
    map:array[0..1005,0..1005]of integer;
    i,j,k,l,n,x,y,now,o,q,t1,t2,ans:longint;
    z,ct:int64;
function min(x,y:longword):longword;
begin
    if x<y then exit(x) else exit(y);
end;
procedure dfs(x,y:longint);
var
    i:longint;
begin
    inc(now);
    new[x]:=now;
    size[now]:=1;
    for i:=1 to map[x,0] do
    if map[x,i]<>y then
    begin
        dfs(map[x,i],x);
        size[new[x]]:=size[new[x]]+size[new[map[x,i]]];
    end;
end;
begin
    assign(input,'plan.in'); reset(input);
    assign(output,'plan.out'); rewrite(output);
    readln(n);
    for i:=1 to n-1 do
    begin
        readln(x,y);
        inc(map[x,0]);
        map[x,map[x,0]]:=y;
        inc(map[y,0]);
        map[y,map[y,0]]:=x;
    end;
    dfs(1,0);
    for i:=1 to n do readln(va[new[i]],co[new[i]]);
    now:=0;
    for i:=1 to 50000 do a[0,i]:=mc;
    for i:=1 to n do
    begin
        now:=now+va[i];
        s1[i]:=now;
        for j:=now downto va[i] do
        begin
            a[i,j]:=a[i-1,j];
            if a[i,j]=0 then a[i,j]:=mc;
            a[i,j]:=min(a[i,j],a[i-1,j-va[i]]+co[i]);
        end;
        for j:=va[i]-1 downto 1 do
        begin
            a[i,j]:=a[i-1,j];
            if a[i,j]=0 then a[i,j]:=mc;
        end;
        ct:=mc;
        for j:=now downto 1 do
        begin
            if a[i,j]<ct then ct:=a[i,j];
            a[i,j]:=ct;
        end;
    end;
    now:=0;
    for i:=1 to 50000 do b[n+1,i]:=mc;
    for i:=n downto 1 do
    begin
        now:=now+va[i];
        s2[i]:=now;
        for j:=now downto va[i] do
        begin
            b[i,j]:=b[i+1,j];
            if b[i,j]=0 then b[i,j]:=mc;
            b[i,j]:=min(b[i,j],b[i+1,j-va[i]]+co[i]);
        end;
        for j:=va[i]-1 downto 1 do
        begin
            b[i,j]:=b[i+1,j];
            if b[i,j]=0 then b[i,j]:=mc;
        end;
        ct:=mc;
        for j:=now downto 1 do
        begin
            if b[i,j]<ct then ct:=b[i,j];
            b[i,j]:=ct;
        end;
    end;
    readln(q);
    for o:=1 to q do
    begin
        readln(x,z);
        x:=new[x];
        y:=x+size[x];
        x:=x-1;
        t1:=0; t2:=s2[y];
        ans:=0;
        while t1<=s1[x] do
        begin
            while (a[x,t1]+b[y,t2]>z) and (t2>0) do dec(t2);
            if a[x,t1]>z then break;
            if t1+t2>ans then ans:=t1+t2;
            inc(t1);
        end;
        writeln(ans);
    end;
    close(input); close(output);
end.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值