BZOJ 2286: [Sdoi2011]消耗战 虚树Dp

title

BZOJ 2286

LUOGU 2495

简化题意:

给出一棵树,每条边有边权。

\(m\) 次询问,每次询问给出 \(k\) 个点,问使得这 \(k\) 个点均不与 \(1\) 号点(根节点)相连的最小代价。

\(n\leqslant 250000,m\geqslant 1,\sum k\leqslant 500000\)

analysis

这道题目,好像许多大佬都是拿它来当做虚树入门讲解的。

所以,这道题目也就是先建虚树再 \(Dp\)

这里只说一下 \(Dp\),因为建虚树很常规,要看虚树讲解的话,到 自为风月马前卒 那里学习一下好了。

\(Dp\) 的话,先考虑暴力 \(Dp\),即 \(m=1\) 的情况。

\(f[x]\) 表示处理完以 \(x\) 为根的树的最小代价。

转移分为两种情况:

  1. 断开自己与父亲的联系,代价为从根到该节点的最小值;

  2. 不考虑该节点(前提是该节点不是询问点),把子树内的所有询问点都断开的代价;

这样的复杂度是 \(O(NM)\) 的,显然过不了,因为 \(\sum k\) 比较小,所以可以构建虚树,构建虚树后直接在虚树上进行上述 \(Dp\),复杂度就被优化成 \(O(2*\sum k_i)\) 了。

code

#include<bits/stdc++.h>

typedef long long ll;
const int maxn=25e4+10;

namespace IO
{
    char buf[1<<15],*fs,*ft;
    inline char getc() { return (ft==fs&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),ft==fs))?0:*fs++; }
    template<typename T>inline void read(T &x)
    {
        x=0;
        T f=1, ch=getchar();
        while (!isdigit(ch) && ch^'-') ch=getchar();
        if (ch=='-') f=-1, ch=getchar();
        while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
        x*=f;
    }

    char Out[1<<24],*fe=Out;
    inline void flush() { fwrite(Out,1,fe-Out,stdout); fe=Out; }
    template<typename T>inline void write(T x)
    {
        if (!x) *fe++=48;
        if (x<0) *fe++='-', x=-x;
        T num=0, ch[20];
        while (x) ch[++num]=x%10+48, x/=10;
        while (num) *fe++=ch[num--];
        *fe++='\n';
    }
}

using IO::read;
using IO::write;

template<typename T>inline T min(T x,T y) { return x<y ? x : y; }

int ver[maxn<<1],edge[maxn<<1],Next[maxn<<1],head[maxn],len;
inline void add(int x,int y,int z)
{
    ver[++len]=y,edge[len]=z,Next[len]=head[x],head[x]=len;
    ver[++len]=x,edge[len]=z,Next[len]=head[y],head[y]=len;
}

std::vector<int>v[maxn];
inline void add_c(int x,int y)
{
    v[x].push_back(y);
}

namespace Virtual
{
    int dfn[maxn],id,dep[maxn],f[maxn][21];
    ll num[maxn];
    inline void dfs(int x)
    {
        dfn[x]=++id;
        for (int i=1; i<=20; ++i) f[x][i]=f[f[x][i-1]][i-1];
        for (int i=head[x]; i; i=Next[i])
        {
            int y=ver[i];
            if (y==f[x][0]) continue;
            f[y][0]=x;
            dep[y]=dep[x]+1;
            num[y]=min(num[x],(ll)edge[i]);
            dfs(y);
        }
    }

    inline int LCA(int x,int y)
    {
        if (dep[x]>dep[y]) std::swap(x,y);
        for (int i=20; i>=0; --i)
            if (dep[y]-(1<<i)>=dep[x]) y=f[y][i];//
        if (x==y) return x;
        for (int i=20; i>=0; --i)
            if (f[x][i]^f[y][i]) x=f[x][i],y=f[y][i];
        return f[x][0];
    }

    inline bool cmp(int a,int b)
    {
        return dfn[a]<dfn[b];
    }

    int Stack[maxn],top;
    inline void insert(int x)
    {
        if (top==1) { Stack[++top]=x; return ; }
        int lca=LCA(Stack[top],x);
        if (lca==Stack[top]) return ;
        while (dfn[Stack[top-1]]>=dfn[lca] && top>1)
        {
            add_c(Stack[top-1],Stack[top]), --top;
        }
        if (lca!=Stack[top]) add_c(lca,Stack[top]), Stack[top]=lca;
        Stack[++top]=x;
    }

    inline void build(int *c,int k)
    {
        std::sort(c+1,c+k+1,cmp);
        Stack[top=1]=1;
        for (int i=1; i<=k; ++i) insert(c[i]);
        while (top>0) add_c(Stack[top-1],Stack[top]), --top;
    }

    inline ll Dp(int x)
    {
        if (!(int)v[x].size()) return num[x];
        ll sum=0;
        for (int i=0; i<(int)v[x].size(); ++i) sum+=Dp(v[x][i]);
        v[x].clear();
        return min(sum,num[x]);
    }
}

using Virtual::dfs;
using Virtual::build;
using Virtual::Dp;

int main()
{
    int n;read(n);
    for (int i=1,x,y,z; i<n; ++i) read(x),read(y),read(z),add(x,y,z);
    Virtual::num[1]=1ll<<60;
    dfs(1);
    int m;read(m);
    while (m--)
    {
        int k;read(k);
        int *c=new int[k+10];
        for (int i=1; i<=k; ++i) read(c[i]);
        build(c,k);
        write(Dp(1));
        delete[] c;
    }
    IO::flush();
    return 0;
}

转载于:https://www.cnblogs.com/G-hsm/p/11382464.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值