https://www.luogu.com.cn/problem/P2495
虚树:当我们在解决树形dp的问题的时候,题目中会给出一些询问,询问涉及的关键节点不多,并保证总的点数规模的时候,我们就可以使用虚数,如果每次询问都对整个树进行dp的话,显然会超市。要注意每一次的初始化
题目:给出n点,有n-1条带权边,有q个询问,每次给出k个点,询问要使得这k个点和1号节点不连通,需要移除的边的总权值最小是多少。
思路:
令dp[n]表示从nn开始不能到达其子树中的关键点所需切断的最小边权和.
令me[u]表示切断11到uu的路径中的边权最小值.
设vv是uu的直接儿子.
如果vv是关键节点,那么dp[u]+=me[v],否则dp[u]+=min(me[v],dp[v])
(第2个转移方程的解释:要么直接切断1−v的路径,要么使得从v出发不能到达其子树的关键点.)
显然我们不能针对每个询问对整颗子树进行dpdp,时间复杂度过高,而我们发现那些非关键点我们没有必要在dp的时候考虑,所以使用虚树.
参考:大神题解
#include <iostream>
#include <cstdio>
#include <fstream>
#include <algorithm>
#include <cmath>
#include <deque>
#include <vector>
#include <queue>
#include <string>
#include <cstring>
#include <map>
#include <stack>
#include <set>
#include <cstdlib>
#define INF 0x3f3f3f3f3f3f3f3f
#define inf 0x3f3f3f3f
#define FILL(a,b) (memset(a,b,sizeof(a)))
#define re register
#define lson rt<<1
#define rson rt<<1|1
#define lowbit(a) ((a)&-(a))
#define ios std::ios::sync_with_stdio(false);std::cin.tie(0);std::cout.tie(0);
#define fi first
#define rep(i,n) for(int i=0;(i)<(n);i++)
#define rep1(i,n) for(int i=1;(i)<=(n);i++)
#define se second
#define scd(a) scanf("%d",&a)
#define scdd(a,b) scanf("%d%d",&a,&b)
#define scddd(a,b,c) scanf("%d%d%d",&a,&b,&c)
#define ac cout<<ans<<"\n"
#define F(x) ((x)/3+((x)%3==1?0:tb))
#define G(x) ((x)<tb?(x)*3+1:((x)-tb)*3+2)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<ll,ll> pii;
int dx[4]= {-1,1,0,0},dy[4]= {0,0,1,-1};
const ll mod=1e9+7;
const ll N =250007;
const ll M =250000;
const double eps = 1e-4;
//const double pi=acos(-1);
ll qk(ll a,ll b){ll ans=1;while(b){if(b&1) ans=(ans*a)%mod;a=(a*a)%mod;b/=2;}return ans%mod;}
int n;
int cnt,m;
int ldfn[N],rdfn[N];//当前节点的dfs序
int fa[N][20];//f[i][j]表示i节点向上2^j步的祖先
int dep[N];//节点深度
int stk[N];//栈,用于构建虚树
int d[N<<1];//用于存储虚树的节点编号。由于LCA可能有重复,因此需要开2倍
int vis[N];//标记当前节点是否为关键点
vector<pii> RG[N];
vector<int> VG[N];
ll me[N];
void predfs(int u,int f,int depth)
{ldfn[u]=++cnt;dep[u]=depth;fa[u][0]=f;for(int j=1;j<20;j++){fa[u][j]=fa[fa[u][j-1]][j-1];}for(pii k:RG[u]){int v=k.fi;if(v==f) continue;me[v]=k.second;if(u!=1) me[v]=min(me[u],me[v]);predfs(v,u,depth+1);}rdfn[u]=cnt;
}int lca(int u,int v)
{if(dep[u]<dep[v])swap(u,v);int delta=dep[u]-dep[v];for(int j=0;j<20&δj++){if(delta&(1<<j))u=fa[u][j];}if(u==v) return u;for(int j=20-1;j>=0;j--){if(fa[u][j]!=fa[v][j]){u=fa[u][j];v=fa[v][j];}}return fa[u][0];
}bool cmp(int x,int y)
{return ldfn[x]<ldfn[y];
}void build()
{sort(d+1,d+m+1,cmp);int keynum=m;for(int i=1;i<keynum;i++) d[++m]=lca(d[i],d[i+1]);sort(d+1,d+m+1,cmp);m=unique(d+1,d+m+1)-d-1;int top=0;stk[++top]=d[1];for(int i=2;i<=m;i++){while(top&&rdfn[stk[top]]<ldfn[d[i]])top--;if(top)VG[stk[top]].push_back(d[i]);stk[++top]=d[i];}
}
ll DP(int u){ll cost=0;for(int v:VG[u]){cost+=min(me[v],DP(v));}VG[u].clear();//初始化if(vis[u]) return me[u];else return cost;
}
void sovle(){cin>>n;for(int i=1;i<n;i++){int u,v,c;cin>>u>>v>>c;RG[u].push_back({v,c});RG[v].push_back({u,c});}predfs(1,0,0);int q;cin>>q;while(q--){cin>>m;m++;d[1]=1;for(int i=2;i<=m;i++){cin>>d[i];vis[d[i]]=1;}build();cout<<DP(1)<<endl;for(int i=2;i<=m;i++) vis[d[i]]=0;//初始化部分很关键cnt=0;}
}
int main()
{
#ifdef LOCALfreopen("in.txt", "r", stdin);
#else// iosint t=1;// cin>>t;while(t--) sovle();
#endif // LOCALreturn 0;
}