BZOJ2286:消耗战(虚树 & 树形dp)

在一场战争中,战场由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
101 5 131 9 62 1 192 4 82 3 915 6 87 5 47 8 3110 7 932 10 64 5 7 8 33 9 4 6
Sample Output
123222
Hint

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


题意:给一棵树,每条边有个权值,然后又M个询问,每个询问给出K个点代表有资源,然后需要去掉若干条边使得1号点与资源点不联通,问去掉的边权总和最小值。每个询问是独立的。

思路:首先考虑树形dp的做法,预处理每个点到根节点的最小的一条边imin[],dp[i]表示断开根节点与i的子树的资源点最小花费,dp[i] = min(imin[i], sigma(dp[j])),j为i的儿子,复杂度O(n)。但是显然不能每个询问都跑O(n)次,观察到总询问的资源点数<=500000,能否每个询问只对那K个点进行dp呢?这就是所谓的虚树,本题一棵虚数包含K个点及他们的所有LCA。建虚树用栈实现,先对K个点按DFS序排序,建虚树时就能保证同一个栈中的点构成一条链,具体见代码。另外本题假如某个资源点i到根节点路径中存在其他资源点j1,j2,j3...,那么显然虚树的根节点取到深度最浅的那个j就行。

# include <iostream>
# include <cstring>
# include <vector>
# include <cstdio>
# include <algorithm>
# define pb push_back
# define mp make_pair
using namespace std;
typedef long long LL;
const int maxn = 3e5+30;
const LL INF = 1LL<<60;
vector<pair<int,int> >G[maxn];
vector<int>g[maxn];
int cnt=0, n, id[maxn], h[maxn], fa[maxn][22];
int a[maxn], st[maxn*2], sta[maxn];
LL dp[maxn], imin[maxn];
void dfs(int cur, int pre)
{
    h[cur] = h[pre] + 1;
    id[cur] = ++cnt;
    fa[cur][0] = pre;
    for(int i=1; (1<<i)<=h[cur]; ++i)
        fa[cur][i] = fa[fa[cur][i-1]][i-1];
    for(int i=0; i<G[cur].size(); ++i)
    {
        int v = G[cur][i].first;
        if(v != pre)
        {
            imin[v] = min(imin[cur], 1LL*G[cur][i].second);
            dfs(v, cur);
        }
    }
}
void dfs2(int cur)
{
    dp[cur] = imin[cur];
    LL s = 0;
    for(int i=0; i<g[cur].size(); ++i)
    {
        int v = g[cur][i];
        dfs2(v);
        s += dp[v];
    }
    if(s && s < dp[cur]) dp[cur] = s;
}
int lca(int v, int u)
{
    if(h[v] > h[u]) swap(v, u);
    for(int i=0; i<20; ++i)
        if(h[u]-h[v]>>i & 1)
        u = fa[u][i];
    for(int i=20; i>=0; --i)
        if(fa[v][i] != fa[u][i])
            v = fa[v][i], u=fa[u][i];
    return v == u?v:fa[v][0];
}
bool cmp(int x, int y){return id[x] < id[y];}
void link(int u, int v, int tag)
{
    if(u == v) return;
    if(sta[u] != tag) sta[u] = tag, g[u].clear();
    if(sta[v] != tag) sta[v] = tag, g[v].clear();
    g[u].pb(v);
}
void work(int tag)
{
    int tot=1, top=1, k;
    scanf("%d",&k);
    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[tot], a[i]) != a[tot])
            a[++tot] = a[i];
    st[top] = 1;
    for(int i=1; i<=tot; ++i)
    {
        int l = lca(st[top], a[i]);
        while(1)
        {
            if(h[l] >= h[st[top-1]])
            {
                link(l, st[top--], tag);
                break;
            }
            link(st[top-1], st[top], tag); --top;
        }
        if(st[top] != l) st[++top] =l ;
        if(st[top] != a[i]) st[++top] = a[i];
    }
    while(--top) link(st[top], st[top+1], tag);
    dfs2(1);
    printf("%lld\n",dp[1]);
}
int main()
{
    int u, v, w, m;
    scanf("%d",&n);
    for(int i=1; i<n; ++i)
    {
        scanf("%d%d%d",&u,&v,&w);
        G[u].pb(mp(v,w));
        G[v].pb(mp(u,w));
    }
    imin[1] = INF; h[0] = -1;
    dfs(1, 0);
    scanf("%d",&m);
    while(m--) work(m);
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值