[Bzoj2286][Sdoi2011]消耗战(虚树模板题附讲解)

2286: [Sdoi2011]消耗战


 

Time Limit: 20 Sec  Memory Limit: 512 MB
Submit: 4896  Solved: 1824
[ Submit][ Status][ Discuss]

Description


 

在一场战争中,战场由n个岛屿和n-1个桥梁组成,保证每两个岛屿间有且仅有一条路径可达。现在,我军已经侦查到敌军的总部在编号为1的岛屿,而且他们已经没有足够多的能源维系战斗,我军胜利在望。已知在其他k个岛屿上有丰富能源,为了防止敌军获取能源,我军的任务是炸毁一些桥梁,使得敌军不能到达任何能源丰富的岛屿。由于不同桥梁的材质和结构不同,所以炸毁不同的桥梁有不同的代价,我军希望在满足目标的同时使得总代价最小。
侦查部门还发现,敌军有一台神秘机器。即使我军切断所有能源之后,他们也可以用那台机器。机器产生的效果不仅仅会修复所有我军炸毁的桥梁,而且会重新随机资源分布(但可以保证的是,资源不会分布到1号岛屿上)。不过侦查部门还发现了这台机器只能够使用m次,所以我们只需要把每次任务完成即可。

Input


 

第一行一个整数n,代表岛屿数量。

接下来n-1行,每行三个整数u,v,w,代表u号岛屿和v号岛屿由一条代价为c的桥梁直接相连,保证1<=u,v<=n且1<=c<=100000。

第n+1行,一个整数m,代表敌方机器能使用的次数。

接下来m行,每行一个整数ki,代表第i次后,有ki个岛屿资源丰富,接下来k个整数h1,h2,…hk,表示资源丰富岛屿的编号。

 

Output


 

输出有m行,分别代表每次任务的最小代价。

 

 

Sample Input


 

10
1 5 13
1 9 6
2 1 19
2 4 8
2 3 91
5 6 8
7 5 4
7 8 31
10 7 9
3
2 10 6
4 5 7 8 3
3 9 4 6

 

Sample Output


 

12
32
22

 

HINT


 

 

 对于100%的数据,2<=n<=250000,m>=1,sigma(ki)<=500000,1<=ki<=n-1

 

Source


 

Stage2 day2

 

虚树:


既然是虚树的版版题,我就有必要提一下什么是虚树了,虽然我也是今天才学的。但我尽量按自己的理解讲清楚吧

 

什么是虚树,就是在树上选定一个集合,这个集合间所有点和他们相应lca之间重新组成的树。

 

设红色点为关键点,让我们来看看最后的虚树

 

橙色点是虚树中关键点的lca,虚点和虚边就是被去掉了。

这里看起来是两条边,其实合成了一条边

 

 

 
虚树,什么时候可以派上用场,就是询问很多,每次询问给定树的一个集合,所有询问集合点数总和不大,且问题可以转化为只和集合关键点有关时。就可以用虚树
 
比如说这道题,我们可以建出虚树,那么就是一个很简单的树形dp了,一个点要么是自己不选,所有儿子选,要么所有儿子都不选自己选。
 
那设一个点选的价值为f[i] ,这个点到1号结点路径上价值最小边为val[i]
那么

 

最后输出虚树根节点(root)即可。

然后我们还发现一个性质就是一个点是关键点,那么它的儿子结点可以直接忽略。

那么说了这道题怎么做了,就差怎么建出虚树了。

建虚树首先我们dfs一遍原树,建出dfs序。

我们先把关键点按从小到大排序。

然后用一个栈的形式维护一条链。

简单说

如果当前点和栈顶为一条链,那么可以直接加入栈。

如果不为一条链,那么得一直删除栈顶,直到形成一条链。

怎么具体实现呢?

设当前点和栈顶lca为g

那么如果dfn[g] >= dfn[top]那么很显然是一条链,直接push当前点即可

如果dfn[g] < dfn[top]那么不是一条链,边pop,边连边栈顶第二个个元素和栈顶。

知道dfn[g] >= dfn[top - 1],那么把top - 1 和 top连边后加入g即可。

最后一条链没有点去pop它,所以最后还要特殊判断栈内是否还要有一条链。

这样就很方便了。

AC代码: 


 

# include <iostream>
# include <cstdio>
# include <cstring>
# include <algorithm>
using namespace std;
typedef long long LL;
const int N = 6e5 + 12;
const LL inf = 1e15;
int hson[N],sz[N],dep[N],top[N],id[N],fa[N];LL f[N];
int head[N],dt,tot,que[N],n,m,k,a[N],cnt;LL w[N];
struct Edge{
    int to,nex;LL w;
}edge[N << 1];
void AddEdge(int u,int v,LL w)
{
    if(u == v)return;
    edge[++dt] = (Edge){v,head[u],w};
    head[u] = dt;
}
void dfs(int u)
{
    sz[u] = 1;
    for(int i = head[u];i;i = edge[i].nex)
    {
        if(sz[edge[i].to])continue;
        dep[edge[i].to] = dep[u] + 1;
        fa[edge[i].to] = u;
        w[edge[i].to] = min(w[u],edge[i].w);
        dfs(edge[i].to);
        sz[u] += sz[edge[i].to];
        if(sz[hson[u]] < sz[edge[i].to])hson[u] = edge[i].to;
    }
}
void dfs(int u,int tp)
{
    top[u] = tp;id[u] = ++tot;
    if(hson[u])dfs(hson[u],tp);
    for(int i = head[u];i;i = edge[i].nex)
    if(!id[edge[i].to])dfs(edge[i].to,edge[i].to);
    head[u] = 0;
}
int lca(int u,int v)
{
    while(top[u] != top[v])
    {
        if(dep[top[u]] < dep[top[v]])swap(u,v);
        u = fa[top[u]];
    }
    return dep[u] < dep[v] ? u : v;
}
bool cmp(int x,int y){return id[x] < id[y];}
void dp(int u)
{
    LL val = 0;f[u] = w[u];
    for(int i = head[u];i;i = edge[i].nex)
    dp(edge[i].to),val += f[edge[i].to];
    if(val)f[u] = val < f[u] ? val : f[u];
    head[u] = 0;
}
void solve()
{
     
    scanf("%d",&k);cnt = 1;int g,top;dt = 0;
    for(int i = 1;i <= k;i++)scanf("%d",&a[i]);
    sort(a + 1,a + k + 1,cmp);
    for(int i = 2;i <= k;i++)if(lca(a[i],a[cnt]) != a[cnt])a[++cnt] = a[i];
    que[top = 1] = 1;
    for(int i = 1;i <= cnt;i++)
    {
        g = lca(a[i],que[top]);
        while(1)
        {
            if(dep[que[top - 1]] <= dep[g])
            {
                AddEdge(g,que[top],0);top--;
                if(que[top] != g)que[++top] = g;
                break;
            }
            AddEdge(que[top - 1],que[top],0);top--;
        }
        if(que[top] != a[i])que[++top] = a[i];
    }
    top--;
    while(top)AddEdge(que[top],que[top + 1],0),top--;
    dp(1);
    printf("%lld\n",f[1]);
}
int main()
{
  scanf("%d",&n);int x,y;LL z;w[1] = inf;
  for(int i = 1;i < n;i++)
  {
    scanf("%d %d %lld",&x,&y,&z);
    AddEdge(x,y,z);AddEdge(y,x,z);
  }
  dfs(1);dfs(1,1);
  scanf("%d",&m);
  while(m--)solve();
}

 

 

 

转载于:https://www.cnblogs.com/lzdhydzzh/p/8639487.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值