[SDOI2011]消耗战

做题时间:2022.7.7

有一棵 N(N2.5×105) 个点、根为 1 的有根树,每一条边有一个边权wi ,现有 m(m5×105) 次询问,第 i 次询问给出 ki(ki5×105) 个关键点,现在删除一些边,使得 ki 个关键点都与根不联通,问删除的边的最小边权和(询问之间无关联)。

第一行一个整数表示 N
接下来 N1 行每行两个整数 u,v 表示树上的每一条边 (u,v)
接下来一行一个整数 m 表示询问次数
接下来 m 行每行第一个数为 ki ,接下来 ki 个数表示本次询问的关键点

m 行每行一个整数表示答案。

虚树、树形DP

不考虑数据规模很容易想到树形DP。

观察数据后发现有多次询问,且 ki5×105 , 若能在Dp中尽量不遍历非关键节点,那么复杂度就接近 O(ki)

像这种DP过程中大量访问非关键节点(对关键节点没有影响),且关键节点数较小的题目,一般都用虚树+DP解决,即重新建立一棵仅包含关键节点及其LCA的树再DP

值得注意的是虚树的边权怎么订。首先可以知道虚树上两点的边权即为在原树中两点的距离最小值,这需要用倍增DP维护且复杂度贼大 ,但这道题目对于一个点而言,切除其与根的联系,必定切除的是其本身与根路径上边权最小的那一条边,因此直接记录虚树上每一个点与根节点路径上边权最小值即可。

接下来就是简单的DP了,定义 fu 表示以点 u 为根的子树的答案,转移方程:

fu=vsonu{min(fv,mwv)vmwvv

mwv 表示的是 v 到根的最小边权

另外要注意的是,如果原图退化成菊花图,那么答案就可能爆int,因此dp数组要开long long,初始值也应当调大。

#include<cstdio>
#include<iomanip>
#include<algorithm>
using namespace std;
const int N=2e5+5e4+50,M=5e5+50;
const long long INF=0x3f3f3f3f3f3f3f3f;
typedef long long ll;
struct edge{
int to,val,nxt;
}a[N*10];
int head[N],dep[N],f[N][30],n,cnt,tot,top;
int id[N],st[N],h[M],k,m;
ll dp[N],Minw[N];
bool Is_h[N];
bool cmp(int a,int b){return id[a]<id[b];}
inline ll Min(ll a,ll b){return a<b?a:b;}
int Read()
{
int x=0;
char ch=getchar();
while(ch>'9'||ch<'0') ch=getchar();
while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+ch-48;
ch=getchar();
}
return x;
}
void add(int u,int v,int w)
{
cnt++;
a[cnt].to=v;
a[cnt].val=w;
a[cnt].nxt=head[u];
head[u]=cnt;
}
void DFS(int u,int fa)
{
id[u]=++tot;
dep[u]=dep[fa]+1;
f[u][0]=fa;
for(int i=1;i<=20;i++) f[u][i]=f[f[u][i-1]][i-1];
for(int i=head[u];i;i=a[i].nxt){
int v=a[i].to;
if(v!=fa) Minw[v]=Min(Minw[u],a[i].val),DFS(v,u);
}
}
void Swap(int &x,int &y){int t=x;x=y;y=t;}
int LCA(int x,int y)
{
if(dep[x]<dep[y]) Swap(x,y);
for(int i=20;i>=0;i--){
if(dep[f[x][i]]>=dep[y]) x=f[x][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];
}
void Work()//虚树的核心代码
{
sort(h+1,h+1+k,cmp);
st[top=1]=1,head[1]=0;
for(int i=1;i<=k;i++){
if(h[i]!=1){
int l=LCA(h[i],st[top]);
//lca分四种情况:在st[top];在st[top]与st[top-1]之间;在st[top-1];在st[top-1]上方
if(id[l]!=id[st[top]]){
while(id[l]<id[st[top-1]]){
add(st[top],st[top-1],0);
add(st[top-1],st[top],0);
top--;
}
if(id[l]>id[st[top-1]]){
head[l]=0,add(l,st[top],0);
add(st[top],l,0);
st[top]=l;
}
else{
add(l,st[top],0);
add(st[top],l,0);
top--;
}
}
}
head[h[i]]=0,st[++top]=h[i];
}
//最后一条链
for(int i=1;i<top;i++){
add(st[i],st[i+1],0);
add(st[i+1],st[i],0);
}
top=0;
}
void DP(int u,int fa)
{
dp[u]=0;
for(int i=head[u];i;i=a[i].nxt){
int v=a[i].to;
if(v!=fa){
DP(v,u);
if(Is_h[v]) dp[u]+=Minw[v];
else dp[u]+=Min(dp[v],Minw[v]);
}
}
}
int main()
{
n=Read();
for(int i=1;i<=n-1;i++){
int u=Read(),v=Read(),w=Read();
add(u,v,w),add(v,u,w);
}
Minw[1]=INF;
DFS(1,0);
m=Read();
while(m--){
scanf("%d",&k);
for(int i=1;i<=k;i++){
scanf("%d",&h[i]);
Is_h[h[i]]=true;
}
Work();
DP(1,0);
for(int i=1;i<=k;i++) Is_h[h[i]]=false;
printf("%lld\n",dp[1]);
}
return 0;
}

本文作者:lxzy

本文链接:https://www.cnblogs.com/Unlimited-Chan/p/16456934.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   lxzy  阅读(35)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
  1. 1 404 not found REOL
404 not found - REOL
00:00 / 00:00
An audio error has occurred.