虚树入门 [SDOI2011]消耗战

题目描述
在一场战争中,战场由n个岛屿和n-1个桥梁组成,保证每两个岛屿间有且仅有一条路径可达。现在,我军已经侦查到敌军的总部在编号为1的岛屿,而且他们已经没有足够多的能源维系战斗,我军胜利在望。已知在其他k个岛屿上有丰富能源,为了防止敌军获取能源,我军的任务是炸毁一些桥梁,使得敌军不能到达任何能源丰富的岛屿。由于不同桥梁的材质和结构不同,所以炸毁不同的桥梁有不同的代价,我军希望在满足目标的同时使得总代价最小。

侦查部门还发现,敌军有一台神秘机器。即使我军切断所有能源之后,他们也可以用那台机器。机器产生的效果不仅仅会修复所有我军炸毁的桥梁,而且会重新随机资源分布(但可以保证的是,资源不会分布到1号岛屿上)。不过侦查部门还发现了这台机器只能够使用m次,所以我们只需要把每次任务完成即可。

输入输出格式
输入格式:
第一行一个整数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,表示资源丰富岛屿的编号。

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

————

抄个模板就能用的东西,不过还是有一定代码量的,需要预处理LCA查询的东西以及题目查询所需要的。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef unsigned long long ull;
#define fi first
#define se second
#define mp make_pair
#define pb push_back
const int maxn=250000+15;
const int inf=0x3f3f3f3f;

int n,m;

struct edge{
    int to,nxt,len;
}e[maxn],xe[maxn];
int head[maxn],xhead[maxn];
int tot,xtot;

void adde(int u,int v,int l){
    e[tot].to=v;
    e[tot].len=l;
    e[tot].nxt=head[u];
    head[u]=tot++;
}

void xadde(int u,int v,int l){
    xe[xtot].to=v;
    xe[xtot].len=l;
    xe[xtot].nxt=xhead[u];
    xhead[u]=xtot++;
}

int dep[maxn],dfn[maxn];
int w[maxn][19];
int fa[maxn][19];
int dfs_clock;
void dfs(int u,int ff,int dd=0){
    dfn[u]=++dfs_clock;
    dep[u]=dd;
    fa[u][0]=ff;
    for(int i=1;i<=18;i++){
        fa[u][i]=fa[fa[u][i-1]][i-1];
    }
    for(int i=head[u];i!=-1;i=e[i].nxt){
        int v=e[i].to;
        if(v==ff)continue;
        int len=e[i].len;
        w[v][0]=len;
        dfs(v,u,dd+1);
    }
}

void init(){
    for(int j=1;j<=18;j++){
        for(int i=1;i<=n;i++){
            fa[i][j]=fa[fa[i][j-1]][j-1];
            w[i][j]=min(w[i][j-1],w[fa[i][j-1]][j-1]);
        }
    }
}

int LCA(int x,int y){
    if(dep[x]<dep[y])swap(x,y);
    for(int i=18;i>=0;i--){
        if(dep[fa[x][i]]>=dep[y])x=fa[x][i];
    }
    if(x==y)return x;
    for(int i=18;i>=0;i--){
        if(fa[x][i]!=fa[y][i]){
            x=fa[x][i];y=fa[y][i];
        }
    }
    return fa[x][0];
}

int getw(int rt,int x){
    int res=inf;
    for(int i=18;i>=0;i--){
        if(dep[fa[x][i]]>dep[rt]){
            res=min(res,w[x][i]);
            x=fa[x][i];
        }
    }
    return min(res,w[x][0]);
}

vector<int> req;
vector<int> treenode;
bool cmp(int a,int b){
    return dfn[a]<dfn[b];
}

int sta[maxn];
int top;

void build_tree(){
    top=0;
    for(auto u:req){
        treenode.pb(u);
        if(top==0){
            sta[++top]=u;
            continue;
        }
        int lca=LCA(sta[top],u);
        treenode.pb(lca);
        while(top>1&&dep[sta[top-1]]>dep[lca]){
            xadde(sta[top-1],sta[top],getw(sta[top-1],sta[top]));
            top--;
        }
        if(dep[lca]<dep[sta[top]]){
            xadde(lca,sta[top],getw(lca,sta[top]));
            top--;
        }
        if(!top||dep[sta[top]]<dep[lca])sta[++top]=lca;
        sta[++top]=u;
    }
    while(top>1){
        xadde(sta[top-1],sta[top],getw(sta[top-1],sta[top]));
        top--;
    }
}

bool del[maxn];

ll DP(int u,int fa){
    if(del[u])return inf;
    ll ret=0;
    for(int i=xhead[u];i!=-1;i=xe[i].nxt){
        int v=xe[i].to;
        if(v==fa)continue;
        ll len=xe[i].len;
        ret+=min(len,DP(v,u));
    }
    return ret;
}

void work(){
    build_tree();
    printf("%lld\n",DP(1,1));
}

int main(){
    memset(head,-1,sizeof(head));
    memset(xhead,-1,sizeof(xhead));
    tot=0;
    scanf("%d",&n);
    for(int i=1,a,b,c;i<n;i++){
        scanf("%d%d%d",&a,&b,&c);
        adde(a,b,c);adde(b,a,c);
    }
    dfs(1,1);
    init();
    scanf("%d",&m);
    for(int i=0,k,a;i<m;i++){
        req.clear();
        scanf("%d",&k);
        for(int j=0;j<k;j++){
            scanf("%d",&a);
            req.pb(a);
            del[a]=1;
        }
        req.pb(1);
        sort(req.begin(),req.end(),cmp);
        work();
        for(auto v:treenode){
            xhead[v]=-1;
        }
        for(auto v:req)del[v]=0;
        treenode.clear();
        xtot=0;
    }


    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值