P2495 [SDOI2011]消耗战

传送门

虚树DP经典题

首先有一个显然的$O(nm)$的树形DP

以 1 号节点为根

设 $f [ x ]$ 表示把节点 $x$ 子树内的资源点都与 $x$ 的父节点断开的最小代价

那么转移显然:

枚举 $x$ 的所有儿子节点 $v$,设 $x$ 到父节点的边权为 $w$

$f [ x ] = min ( w,\sum_vf[v] )$

然后发现 $\sum_{k_i}\leq 500000$

所以显然要搞虚树

每次DP只要把有用的节点提出来,根据它们之间的父子关系建一颗虚树

DP只要在虚树上DP就可以求出答案

对于虚树上的边权容易想到取原树上对应的一段路径的边权最小值,也容易想到倍增求这个最小值

但是我们可以贪心一下来减少代码难度:

直接维护从当前节点到根的边权最小值,如果最小值在路径就跟上面的没区别

如果在更离根更近的位置上显然删掉此边会比删路径上的最小值更优

然后就可以构建虚树了,构造过程就不细讲了

求LCA我用的是ST表

总复杂度 $O(\sum_{k_i}+nlog_n)$

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<vector>
using namespace std;
typedef long long ll;
inline int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<‘0‘||ch>‘9‘) { if(ch==‘-‘) f=-1; ch=getchar(); }
    while(ch>=‘0‘&&ch<=‘9‘) { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
    return x*f;
}
const int N=5e5+7;
const ll INF=1e16+7;
int fir[N],from[N<<1],to[N<<1],val[N<<1],cntt;//存原树
inline void add(int &a,int &b,int &c)
{
    from[++cntt]=fir[a]; fir[a]=cntt;
    to[cntt]=b; val[cntt]=c;
}
int n,m;
int st[N<<1],Top,dfn[N],cnt,pos[N<<1];//pos存节点第一次在欧拉序中出现的位置,用于ST表求LCA
ll mi[N];//存节点到根的边权最小值
void dfs(int x,int fa)//dfs预处理各种东西
{
    dfn[x]=++cnt;
    st[++Top]=x; pos[x]=Top;
    for(int i=fir[x];i;i=from[i])
    {
        int &v=to[i]; if(v==fa) continue;
        mi[v]=min(mi[x],1ll*val[i]);//先更新mi
        dfs(v,x); st[++Top]=x;//再dfs下去,出来后x要再入一次栈
    }
}
int f[N<<1][21],Log[N<<1];//ST表
void pre()//预处理ST表
{
    Log[0]=-1; for(int i=1;i<=Top;i++) Log[i]=Log[i>>1]+1;
    for(int i=1;i<=Top;i++) f[i][0]=st[i];
    for(int i=1;(1<<i)<=Top;i++)
    {
        for(int j=1;j+(1<<i-1)<=Top;j++)
        {
            if(dfn[f[j][i-1]]<dfn[f[j+(1<<i-1)][i-1]]) f[j][i]=f[j][i-1];
            else f[j][i]=f[j+(1<<i-1)][i-1];
        }
    }
}
inline int query(int x,int y)//求节点x,y的LCA
{
    int l=pos[x],r=pos[y],k;
    if(l>r) swap(l,r);
    k=Log[r-l+1];
    if(dfn[f[l][k]]<dfn[f[r-(1<<k)+1][k]]) return f[l][k];
    else return f[r-(1<<k)+1][k];
}
vector <int> v[N];//vector存虚树
inline void Add(int x,int y) { v[x].push_back(y); }//虚树上连边
inline void ins(int x)//一个个插入构造虚树
{
    if(Top==1) { st[++Top]=x; return; }
    int lca=query(x,st[Top]);
    if(lca==st[Top]) return;//可以发现如果x与st[Top]在同一条链上那么x就没用了,因为st[Top]上面肯定要割边
    while(Top>1 && dfn[st[Top-1]]>=dfn[lca]) Add(st[Top-1],st[Top]),Top--;//如果处理完一条链就加入虚树并弹出节点
    if(lca!=st[Top]) Add(lca,st[Top]),st[Top]=lca;//考虑把lca入栈
    st[++Top]=x;//最后加入x
}
ll dp(int x)//虚树上DP
{
    int len=v[x].size();
    if(!len) return mi[x];
    ll sum=0;
    for(int i=0;i<len;i++)
        sum+=dp(v[x][i]);
    v[x].clear();//记得DP完把虚树删掉
    return min(sum,mi[x]);
}
int t,d[N];
inline bool cmp(const int &a,const int &b) { return dfn[a]<dfn[b]; }//按dfn排序
int main()
{
    n=read(); int a,b,c;
    for(int i=1;i<n;i++)
    {
        a=read(),b=read(),c=read();
        add(a,b,c); add(b,a,c);
    }
    mi[1]=INF; dfs(1,1);
    pre(); m=read();
    for(int i=1;i<=m;i++)
    {
        t=read();
        for(int j=1;j<=t;j++) d[j]=read();
        sort(d+1,d+t+1,cmp); st[Top=1]=1;
        for(int j=1;j<=t;j++) ins(d[j]);//一个个加入
        while(Top) Add(st[Top-1],st[Top]),Top--;//最后还有一条链没加入虚树
        printf("%lld\n",dp(1));
    }
    return 0;
}

原文地址:https://www.cnblogs.com/LLTYYC/p/10199860.html

时间: 2024-11-09 05:50:16

P2495 [SDOI2011]消耗战的相关文章

luogu P2495 [SDOI2011]消耗战

二次联通门 : luogu P2495 [SDOI2011]消耗战 /* luogu P2495 [SDOI2011]消耗战 虚树+树形dp 首先对原图构建出虚树 在建图的时候处理出最小值 转移即可 小技巧 在dp的过程中可以顺便把边表清空 将边结构体封装可方便的建多张图 */ #include <cstdio> #include <iostream> #include <cstring> #include <algorithm> #define INF 1

●洛谷P2495 [SDOI2011]消耗战

题链: https://www.luogu.org/problemnew/show/P2495题解: 虚树入门,树形dp 推荐博客:http://blog.csdn.net/lych_cys/article/details/50814948 代码: #include<bits/stdc++.h> #define MAXN 250005 #define INFll 0x3f3f3f3f3f3f3f3fll using namespace std; int N,Q,M; bool mark[MAX

洛谷 P2495 [SDOI2011]消耗战

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

P2495 [SDOI2011]消耗战 虚树

这是我做的第一道虚树题啊,赶脚不错.其实虚树也没什么奇怪的,就是每棵树给你一些点,让你多次查询,但是我不想每次都O(n),所以我们每次针对给的点建一棵虚树,只包含这些点和lca,然后在这棵虚树上进行树形dp,维护每个点的最小连边权值,这样的复杂度就会降低不少.这里我写了两种写法(其实都是抄的),一种是正常建树的正常做法,还有一种是不用建树,只用堆维护,模拟一棵树的操作,维护欧拉序,就是一个点有进入的编号,也有出去的编号.这样就可以不用真正建出虚树而能进行查询. 题干: 题目描述 在一场战争中,战

AC日记——[SDOI2011]消耗战 洛谷 P2495

[SDOI2011]消耗战 思路: 建虚树走树形dp: 代码: #include <bits/stdc++.h> using namespace std; #define INF 1e17 #define maxn 250005 #define ll long long #define maxm (maxn<<1) struct LandType { ll id,key; bool operator<(const LandType pos)const { return key

bzoj 2286 [Sdoi2011]消耗战(虚树+树上DP)

2286: [Sdoi2011]消耗战 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 1276  Solved: 445[Submit][Status][Discuss] Description 在一场战争中,战场由n个岛屿和n-1个桥梁组成,保证每两个岛屿间有且仅有一条路径可达.现在,我军已经侦查到敌军的总部在编号为1的岛屿,而且他们已经没有足够多的能源维系战斗,我军胜利在望.已知在其他k个岛屿上有丰富能源,为了防止敌军获取能源,我军的任务是炸

[Sdoi2011]消耗战

2286: [Sdoi2011]消耗战 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 3108  Solved: 1115[Submit][Status][Discuss] Description 在 一场战争中,战场由n个岛屿和n-1个桥梁组成,保证每两个岛屿间有且仅有一条路径可达.现在,我军已经侦查到敌军的总部在编号为1的岛屿,而且他们已经 没有足够多的能源维系战斗,我军胜利在望.已知在其他k个岛屿上有丰富能源,为了防止敌军获取能源,我军的任

【BZOJ2286】[Sdoi2011]消耗战 虚树

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

&lt;虚树+树型DP&gt; SDOI2011消耗战

<虚树+树型DP> SDOI2011消耗战 #include <iostream> #include <cstdio> #include <cstring> #include <cmath> #include <algorithm> using namespace std; typedef long long LL; const int MAXN = 25e4 + 10; inline LL in() { LL x = 0, flag