「Luogu2495」 [SDOI2011]消耗战 虚树
Luogu P2495 [SDOI2011]消耗战
Solution
苦思冥想稍作思考之后可以得到一个树形DP的方法:
令\(w(u,v)\)表示u,v之间的边的权值,\(f[u]\)表示以\(u\)为根的子树(不含\(u\))中所有关键点与根断开的最小代价,则转移方程为:
\[f[u]=\begin{cases}w(u,v)&\text{如果v是关键点}\\min(w(u,v),dp[v])&\text{如果v非关键点}\end{cases}
\]
复杂度为\(O(nm)\),显然不正确
对于每次询问可以建立一颗虚树,边权为关键点之间最小的权值
在这道题中,如果一个关键点\(u\)的祖先中有关键点\(v\),那么\(u\)是不需要加入虚树的,因为\(v\)一定需要断开,同时也就断开了\(u\)
Code
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <iostream>
#define maxn 250005
using namespace std;
typedef long long ll;
const ll inf=0x7f7f7f7f7f7f7f7f;
int n,m,k,h[maxn];
int dep[maxn],dfn[maxn],sign,prt[maxn][21],key[maxn];
ll dp[maxn],mnc[maxn][21];
struct edge
{
int u,v,nxt;
ll w;
};
namespace Ori
{
edge g[maxn*2];
int head[maxn],ecnt;
void eADD(int u,int v,ll w)
{
g[++ecnt].u=u;g[ecnt].v=v;g[ecnt].w=w;g[ecnt].nxt=head[u];head[u]=ecnt;
}
}
namespace New
{
edge g[maxn];
int head[maxn],ecnt;
void eADD(int u,int v,ll w)
{
g[++ecnt].u=u;g[ecnt].v=v;g[ecnt].w=w;g[ecnt].nxt=head[u];head[u]=ecnt;
}
void Inti()
{
for(register int i=1;i<=ecnt;++i)
g[i].u=g[i].v=g[i].nxt=0,g[i].w=0LL;
ecnt=0;
}
}
void dfs(int u,int fa,int depth,ll w)
{
dep[u]=depth,prt[u][0]=fa,mnc[u][0]=w,dfn[u]=++sign;
for(register int i=1;(1<<i)<=depth;++i)
{
prt[u][i]=prt[prt[u][i-1]][i-1];
mnc[u][i]=min(mnc[u][i-1],mnc[prt[u][i-1]][i-1]);
}
for(register int i=Ori::head[u];i;i=Ori::g[i].nxt)
{
int v=Ori::g[i].v;
if(v==fa)
continue;
dfs(v,u,depth+1,Ori::g[i].w);
}
}
int LCA(int x,int y)
{
if(dep[x]<dep[y])
swap(x,y);
for(register int i=20;i>=0;--i)
if(dep[prt[x][i]]>=dep[y])
x=prt[x][i];
if(x==y)
return x;
for(register int i=20;i>=0;--i)
if(prt[x][i]!=prt[y][i])
x=prt[x][i],y=prt[y][i];
return prt[x][0];
}
bool cmp(const int &a,const int &b)
{
return dfn[a]<dfn[b];
}
ll getmnc(int u,int v)
{
ll re=inf;
if(dep[u]<dep[v])
swap(u,v);
for(register int i=20;i>=0;--i)
if(dep[prt[u][i]]>=dep[v])
re=min(re,mnc[u][i]),u=prt[u][i];
return re;
}
int top,stk[maxn];
void Build()
{
for(register int i=1;i<=k;++i)
{
if(top==1)
{
stk[++top]=h[i];
continue;
}
int lca=LCA(stk[top],h[i]);
if(lca==stk[top])
continue;
while(top>1 && dfn[stk[top-1]]>=dfn[lca])
New::eADD(stk[top-1],stk[top],getmnc(stk[top-1],stk[top])),--top;
if(lca!=stk[top])
New::eADD(lca,stk[top],getmnc(lca,stk[top])),stk[top]=lca;
stk[++top]=h[i];
}
while(top-1)
New::eADD(stk[top-1],stk[top],getmnc(stk[top],stk[top-1])),--top;
}
void DP(int u)
{
dp[u]=0;
if(!New::head[u])
return;
for(register int i=New::head[u];i;i=New::g[i].nxt)
{
int v=New::g[i].v;
DP(v);
if(key[v])
dp[u]+=New::g[i].w;
else
dp[u]+=min(New::g[i].w,dp[v]);
}
New::head[u]=0;
}
int main()
{
scanf("%d",&n);
for(register int i=1;i<n;++i)
{
int u,v;
ll w;
scanf("%d%d%lld",&u,&v,&w);
Ori::eADD(u,v,w),Ori::eADD(v,u,w);
}
dfs(1,0,1,inf);
scanf("%d",&m);
while(m--)
{
scanf("%d",&k);
for(register int i=1;i<=k;++i)
{
scanf("%d",&h[i]);
key[h[i]]=1;
}
sort(h+1,h+k+1,cmp);
top=0;stk[++top]=1;
New::Inti();
Build();
DP(1);
printf("%lld\n",dp[1]);
for(register int i=1;i<=k;++i)
key[h[i]]=0;
}
return 0;
}