洛谷_2495 [SDOI2011]消耗战(虚树)

消耗战

题目链接:https://www.luogu.com.cn/problem/P2495

题解:

对于单样例,可以考虑树形DP。
但此题是多实例,所以需要对树进行处理,每次询问有k+1(加上一号点)个关键点。对每个询问构造出虚树。
边权:虚树的边上的权值,为原树中两点路径上的边权最小值,可以利用倍增的思想求权值。
DP:对于虚树,设 d p i dp_i dpi为以 i i i为根的子树都与i号节点断开连接的最小代价。对于 i i i的每个子节点 v v v,若v为关键点,则 d p i + = d i s ( i , v ) dp_i+=dis(i,v) dpi+=dis(i,v);若v不是关键点,则 d p i + = m i n ( d i s ( i , v ) , d p v ) dp_i+=min(dis(i,v), dp_v) dpi+=min(dis(i,v),dpv)

#include<stdio.h>
#include<iostream>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<set>
#include<vector>
#include<queue>
#include<iterator>
#define dbg(x) cout<<#x<<" = "<<x<<endl;
#define INF 0x3f3f3f3f
#define eps 1e-7
 
using namespace std;
typedef long long LL;   
typedef pair<int, int> P;
const int maxn = 300100;
const int mod = 998244353;
int top, dep[maxn], fa[maxn][30], dis[maxn][30], a[maxn], vis[maxn], st[maxn];
int tot, cnt, dfn[maxn], hd1[maxn], hd2[maxn], nex[maxn*10], to[maxn*10], w[maxn*10];
void init(int n);
void Insert(int u);
LL solve(int u);
int LCA(int u, int v);
void add(int f, int t, int p, int hd[]);
void dfs(int u, int d, int f);
bool cmp(int a, int b);
int DIS(int u, int v);

int main()
{
    tot = cnt = 1;
    memset(hd1, -1, sizeof(hd1));
    memset(hd2, -1, sizeof(hd2));
    int t, n, i, j, k, q, m;
    scanf("%d", &n);
    for(i=1;i<n;i++){
        int p;
        scanf("%d %d %d", &j, &k, &p);
        add(j, k, p, hd1);
        add(k, j, p, hd1);
    }
    init(n);
    scanf("%d", &q);
    while(q--)
    {
        cnt = 0;
        scanf("%d", &m);
        for(i=0;i<m;i++){
            scanf("%d", &a[i]);
            vis[a[i]] = 1;
        }
        a[m++] = 1;
        sort(a, a+m, cmp);
        top = 0;
        for(i=0;i<m;i++)
            Insert(a[i]);
        while(top>1){
            add(st[top-1], st[top], DIS(st[top-1], st[top]), hd2);
            top--;
        }
        LL ans = solve(1);
        
        printf("%lld\n", ans);
    }
    return 0;
}

bool cmp(int a, int b)
{
    return dfn[a]<dfn[b];
}
//构造虚树,
void Insert(int u)
{
    if(top == 0){
        st[++top] = u;
        return;
    }
    int lca = LCA(u, st[top]);
    while(top>1 && dep[lca]<dep[st[top-1]])
    {
        add(st[top-1], st[top], DIS(st[top-1], st[top]), hd2);
        top--;
    }
    if(dep[lca]<dep[st[top]])add(lca, st[top--], DIS(lca, st[top]), hd2);
    if((!top) || st[top]!=lca)st[++top] = lca;
    st[++top] = u;
}

LL solve(int u)
{
    LL mi, sum = 0;
    for(int i=hd2[u];i!=-1;i=nex[i]){
        mi = solve(to[i]);
        if(vis[to[i]])
            sum += w[i];
        else
            sum += min((LL)w[i], mi);
        vis[to[i]] = 0;
    }
    hd2[u] = -1;
    return sum;
}

int DIS(int u, int v)
{
    if(dep[u]>dep[v])swap(u, v);
    int k = dep[v]-dep[u], sum = INF;
    for(int i=20;i>=0;i--)
        if((k>>i) & 1){
            sum = min(sum, dis[v][i]);
            v = fa[v][i];
        }
    return sum;
}

void add(int f, int t, int p, int hd[])
{
    to[++cnt] = t;
    w[cnt] = p;
    nex[cnt] = hd[f];
    hd[f] = cnt;
}

void dfs(int u, int d, int f)
{
    dep[u] = d;
    fa[u][0] = f;
    dfn[u] = tot++;
    for(int i=hd1[u];i!=-1;i=nex[i])
        if(to[i] != f){
            dfs(to[i], d+1, u);
            dis[to[i]][0] = w[i];
        }
}

void init(int n)
{
    dfs(1, n, 0);
    for(int j=1;j<=22;j++)
        for(int i=1;i<=n;i++){
            fa[i][j] = fa[fa[i][j-1]][j-1];
            dis[i][j] = min(dis[fa[i][j-1]][j-1], dis[i][j-1]);
        }
            
}

int LCA(int u, int v)
{
    if(dep[u] > dep[v])swap(u, v);
    int i, j, k = dep[v]-dep[u];
    for(i=20;i>=0;i--)
        if(k&(1<<i))v = fa[v][i];
    if(u == v)return u;
    for(i=20;i>=0;i--)
        if(fa[u][i] != fa[v][i])
            v = fa[v][i], u = fa[u][i];
    return fa[u][0];
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值