P2495 [SDOI2011] 消耗战

P2495 [SDOI2011] 消耗战

题意简述:

在一颗树上,每次给定k个关键点,求切断1到这些点的最小花费的总和(最小化总花费)

我们考虑dp:记 dp[x]为切断dp[x]内的所有点到1的路径的最小花费
显然我们有两种切法,一种是对每个子树下的dp[to] 求和,另一种是记录一个mincut[x],表示1->x这段路上的所有边中最小的边权

但这样的时间复杂度是O(nq)的,显然不行

那我们该怎么办呢?

虚树闪亮登场:

虚树,顾名思义是一颗虚构的树
它只记录了关键节点,在本题中,注意到

ki5×105

所以我们考虑建立一颗树,上面只包含关键点和他们的lca

虚树的建立:

参考dalao题解:

Link

只要我们建立了虚树,然后直接对每个虚树结点进行树上dp然后我们这题就做完了

记得每次做完之后清空数组和之前虚树建的边!!!

Code

#include<bits/stdc++.h>
#define int long long
const int N=5e5+5;
const int inf=1e17;
const int lg=20;
using namespace std;
int n,m,e1_cnt,e2_cnt,dfn_cnt,top;
int head1[N],head2[N],mincut[N],dfn[N],que[N],tag[N];
int dep[N],st[N];
int f[N][lg+5];
struct Edge{
int to,nxt,w;
}e1[N<<1],e2[N<<1];
void add(int x,int y,int w,Edge e[],int head[],int &cnt)
{
e[++cnt]={y,head[x],w};
head[x]=cnt;
}
void dfs1(int x)
{
dfn[x]=++dfn_cnt;
for(int i=1;i<=lg;i++)
{
f[x][i]=f[f[x][i-1]][i-1];
}
for(int i=head1[x],to,w;i;i=e1[i].nxt)
{
to=e1[i].to,w=e1[i].w;
if(!dfn[to])
{
dep[to]=dep[x]+1;
mincut[to]=min(mincut[x],w);
f[to][0]=x;
dfs1(to);
}
}
}
int LCA(int x,int y)
{
if(dep[x]<dep[y])swap(x,y);
for(int i=lg;i>=0;i--)
{
if(dep[y]<=dep[f[x][i]])x=f[x][i];
}
if(x==y)return x;
for(int i=lg;~i;i--)
{
if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];
}
return f[x][0];
}
int dfs2(int x)
{
int sum=0,res=inf;
for(int i=head2[x],to;i;i=e2[i].nxt)
{
to=e2[i].to;
sum+=dfs2(to);
}
if(tag[x])res=mincut[x];
else res=min(mincut[x],sum);
tag[x]=0;
head2[x]=0;
return res;
}
bool cmp(int x,int y)
{
return dfn[x]<dfn[y];
}
void solve()
{
int k;
scanf("%lld",&k);
for(int i=1;i<=k;i++)
{
scanf("%lld",&que[i]);
tag[que[i]]=1;
}
sort(que+1,que+1+k,cmp);
top=1;
st[top]=que[1];
for(int i=2;i<=k;i++)
{
int now=que[i];
int lca=LCA(now,st[top]);
while(1)
{
if(dep[lca]>=dep[st[top-1]]) //case1 or case2 or case3
{
if(lca!=st[top])//case2 or case 3
{
add(lca,st[top],0,e2,head2,e2_cnt);
if(lca!=st[top-1])//case2
{
st[top]=lca;//st.pop(top) and st.push(lca)
}
else //case3
{
top--;
}
}
// else : case 1
break;
}
else//case 4
{
add(st[top-1],st[top],0,e2,head2,e2_cnt);
top--;
}
}
st[++top]=now;//case 1 2 3 4
}
while(--top)
{
add(st[top],st[top+1],0,e2,head2,e2_cnt);
}
int ans=dfs2(st[1]);
printf("%lld\n",ans);
e2_cnt=1;
}
void work()
{
mincut[1]=inf;
cin>>n;
for(int i=1,x,y,w;i<n;i++)
{
scanf("%lld%lld%lld",&x,&y,&w);
add(x,y,w,e1,head1,e1_cnt);
add(y,x,w,e1,head1,e1_cnt);
}
dfs1(1);
cin>>m;
for(int i=1;i<=m;i++)
{
solve();
}
}
#undef int
int main()
{
//freopen("P2495.in","r",stdin);//freopen("P2495.out","w",stdout);
work();
}
posted @   liuboom  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示