BZOJ2286: [Sdoi2011]消耗战(虚树)


BZOJ2286: [Sdoi2011]消耗战##

  Time Limit: 20 Sec
  Memory Limit: 512 MB

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
  

题目地址:  BZOJ2286: [Sdoi2011]消耗战

题目大意: 已经很简洁了

题解:

  裸的虚树
  
  虚树的概念
  虚树,就是在有一棵树的情况下,对于数量较少的点进行询问时所建的一棵新的树,虚树包含询问的点和询问的点的lca(最近公共祖先),上面的点被称为关键点。对于两个关键点A,B,它们的连边上包含着原本树上两点之间那条链上的关键信息(这个信息可以是边权最大值、边权最小值、或者是边权之和,这个取决于实际需要),然后就可以进行树形dp了,这样的复杂度是基于询问的点数的,就可以想象虚树就是把一棵大树浓缩成一棵拥有所有你需要的信息的小树。
  
  建树的方法
那么怎么建树呢,比较常见的做法是维护一个栈,里面存储着一条链,每一次加入一个点进行操作。
具体列举一下步骤吧
  
  预备知识:
  用较优的复杂度求lca,以及求两点之间的距离,一般倍增做,或者树链剖分加前缀和都可以,这都是单次O(logn)的,另外呢,要事先求好原树的dfs序和每个点的深度(这里的深度是指点序的深度,在计算这个深度的时候每条边都是为1来算),后面要用
  
  询问操作:
  1.输入每个询问的点,并且按照dfs序为关键字排序
  2.将第1个点压到栈当中,开始构建虚树
  3.枚举到下一个点u,计算u与栈顶点v的公共祖先lca
  4.假设栈中栈顶下方的点为w(若栈中只有1个点就直跳过这一步),若w点的深度大于lca就把v向w连一条边,并且弹掉v,重复此步,否则就到下一步
  5.若lca不是当前的v,那么就把lca和v连边,把v弹出,让lca成为栈顶元素(注:这个操作的意思是如果栈顶没有这个lca那么就压入),否则不做任何操作
  6.最后把u压入栈中
  7.回到3操作枚举下个点,直到枚举完了所有点
  8.把栈顶v与栈顶下方的点为w连边,并且把v弹掉,这么做直到栈里只有一个点
  9.栈里剩下的点就是虚树的根了
  接下来你就可以开始进行dp等操作了
  
  虚树的复杂度
  虚树的建树的复杂度是O(k∗log(n))的,树形dp就是O(k)的啦,因为考虑最后虚树上的关键点有询问的点,和lca,然后每个询问的点最多产生1个新的lca,所以复杂度就是对的啦
  
  来自https://blog.csdn.net/zhouyuheng2003/article/details/79110326
  大神的博客里写的很清楚了,具体看代码实现吧:)
  
  此题很容易发现用 dp 解决
  但是多组询问 (O(nm)) 会TLE
  重新建图把点缩少再跑 dp 就好了
  具体看代码实现吧:)


AC代码

#include <cstdio> 
#include <cstring>
#include <algorithm>
#define ll long long
using namespace std;
const int N=250005;
const ll inf=5e10;
int n,Q,K,top,ind;
int a[N],h[N];
int cnt,_cnt,last[N],_last[N];
inline int read(){
    int x=0;char ch=getchar();
    while(ch<'0'||ch>'9')ch=getchar();
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x;
}
struct edge{
    int to,val,next;
}e[N<<1];
struct _edge{
    int to,next;
}_e[N];
void add_edge(int u,int v,int w){
    e[++cnt]=(edge){v,w,last[u]};last[u]=cnt;
    e[++cnt]=(edge){u,w,last[v]};last[v]=cnt;
}
void _add_edge(int u,int v){
    if(u==v)return;
    _e[++_cnt]=(_edge){v,_last[u]};_last[u]=_cnt;
}
int pos[N],dep[N],fa[N][20];
ll mn[N];
void dfs(int u){
    pos[u]=++ind;
    for(int i=last[u];i;i=e[i].next){
        int v=e[i].to;
        if(v==fa[u][0])continue;
        fa[v][0]=u;dep[v]=dep[u]+1;
        mn[v]=min(mn[u],(ll)e[i].val);
        dfs(v);
    }
}
int lca(int a,int b){
    if(dep[a]<dep[b])swap(a,b);
    for(int i=18;i>=0;i--)
        if(dep[fa[a][i]]>=dep[b])
            a=fa[a][i];
    for(int i=18;i>=0;i--)
        if(fa[a][i]!=fa[b][i])
            a=fa[a][i],b=fa[b][i];
    if(a==b)return a;
    return fa[a][0];
}
bool cmp(int a,int b){
    return pos[a]<pos[b];
}
ll f[N];
void DP(int u){
    f[u]=mn[u];
    ll tmp=0;
    for(int i=_last[u];i;i=_e[i].next){
        int v=_e[i].to;
        DP(v);tmp+=f[v];
    }
    _last[u]=0;
    if(tmp!=0)
        f[u]=min(f[u],tmp);
}
int q[N];
void solve(){
    _cnt=0;
    K=read();
    for(int i=1;i<=K;i++)
        h[i]=read();
    sort(h+1,h+K+1,cmp);
    int tot=0;
    h[++tot]=h[1];
    for(int i=2;i<=K;i++)
        if(lca(h[tot],h[i])!=h[tot])
            h[++tot]=h[i];
    top=0;q[++top]=1;
    for(int i=1;i<=tot;i++){
        int now=h[i],F=lca(now,q[top]);
        while(1){
            if(dep[F]>=dep[q[top-1]]){
                _add_edge(F,q[top--]);
                if(q[top]!=F)q[++top]=F;
                break;
            }
            _add_edge(q[top-1],q[top]);top--;
        }
        if(q[top]!=now)q[++top]=now;
    }
    while(--top)
        _add_edge(q[top],q[top+1]);
    DP(1);
    printf("%lld
",f[1]);
}
int main(){
    n=read();
    for(int i=1;i<n;i++){
        int u=read(),v=read(),w=read();
        add_edge(u,v,w);
    }
    mn[1]=inf;dep[1]=1;
    dfs(1);
    for(int j=1;j<=18;j++)
        for(int i=1;i<=n;i++)
            fa[i][j]=fa[fa[i][j-1]][j-1];
    Q=read();
    while(Q--)
        solve();
    return 0;
}


  作者:skl_win
  出处:https://www.cnblogs.com/shaokele/
  本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

原文地址:https://www.cnblogs.com/shaokele/p/9507627.html