BZOJ2286 [SDOI2011]消耗战
2018.01.03
2018.01.03
给你一颗树,树上每条边有边权,M次操作,每次从树上选出K个点,请选择一些边使得选定的每个点到1号点的路径上都至少经过一个点。求最小的总边长。
选出的总点数$\le 500000$
首先这道题多次询问一棵树的性质,显然即使有每次$O(n)$的树形DP解法也是不现实的。所以需要进行优化。
因为选出的点个数满足O(选定点个数)的算法可过,所以有一个大胆的想法:构建一颗新树——保留树上可用信息的同时删除冗余信息,构建的时间复杂度为O(选定点个数)就可以满足条件了。
这就是虚树。
以本题为例,求出所选点和他们之间两两的LCA(易知可以通过求相邻两点LCA的方式求出所有可能的LCA),之后之间连边权值大小为路径上最小边(贪心删除最小的是最优策略),之后跑树形DP就可以了。
具体实现看代码,Sengxian聚聚的博客讲解得非常好。
#include <cstdio>
#include <cctype>
#include <algorithm>
using namespace std;
#define R register
#define N 250010
inline char nc()
{
static char buf[100000],*p1,*p2;
return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}
inline int RI()
{
R int ret=0;R char tmp=nc();
while(!isdigit(tmp)) tmp=nc();
while(isdigit(tmp)) ret=ret*10+(tmp-'0'),tmp=nc();
return ret;
}
int n;
int rk[N],cnt,depth[N],fa[N][20],dis[N][20],lg[N],p[N],st[N],top;
int to[N<<2],val[N<<1],nxt[N<<2],head[N],headb[N],tot,totb,pt[N<<1];
long long f[N];
bool v[N];
inline void AE(R int X,R int Y,R int Z){to[++tot]=Y,val[tot]=Z,nxt[tot]=head[X],head[X]=tot;}
inline void AE(R int X,R int Y){if(!X||!Y)return;to[++tot]=Y,nxt[tot]=headb[X],headb[X]=tot;}
inline bool cmp(R int X,R int Y){return rk[X]<rk[Y];}
inline int LCA(R int X,R int Y)
{
if(depth[X]>depth[Y]) swap(X,Y);
for(int i=lg[n];~i;i--) if(depth[fa[Y][i]]>=depth[X]) Y=fa[Y][i];
for(int i=lg[n];~i;i--) if(fa[X][i]!=fa[Y][i]) X=fa[X][i],Y=fa[Y][i];
return X==Y?X:fa[X][0];
}
inline long long getdis(R int X,R int Y)
{
if(X==Y) return 0;
int ret=0x3f3f3f3f;
if(depth[X]>depth[Y]) swap(X,Y);
for(int i=lg[n];~i;i--) if(depth[fa[Y][i]]>=depth[X]) ret=min(ret,dis[Y][i]),Y=fa[Y][i];
for(int i=lg[n];~i;i--) if(fa[X][i]!=fa[Y][i]) ret=min(ret,dis[X][i]),ret=min(ret,dis[Y][i]),X=fa[X][i],Y=fa[Y][i];
if(X!=Y) ret=min(ret,dis[X][0]),ret=min(ret,dis[Y][0]);
return ret;
}
void init(R int pos,R int pre)
{
rk[pos]=++cnt;
for(R int i=1;i<=lg[n];++i) fa[pos][i]=fa[fa[pos][i-1]][i-1],dis[pos][i]=min(dis[pos][i-1],dis[fa[pos][i-1]][i-1]);
for(R int i=head[pos];i;i=nxt[i]) if(to[i]!=pre) fa[to[i]][0]=pos,dis[to[i]][0]=val[i],depth[to[i]]=depth[pos]+1,init(to[i],pos);
}
void DP(R int pos,R int pre)
{
f[pos]=0;
if(!v[pos]) {for(R int i=headb[pos];i;i=nxt[i])if(to[i]!=pre) DP(to[i],pos),f[pos]+=min(f[to[i]],getdis(pos,to[i]));}
else f[pos]=0x3f3f3f3f;
return;
}
int main()
{
R int m,A,B,C,K;
n=RI();
for(int i=2;i<=n;++i) A=RI(),B=RI(),C=RI(),AE(A,B,C),AE(B,A,C),lg[i]=lg[i>>1]+1;
depth[0]=-1,init(1,0);
m=RI();
totb=tot;
while(m--)
{
K=RI();
for(R int i=1;i<=K;++i) p[i]=RI(),v[p[i]]=true;
p[++K]=1;
sort(p+1,p+K+1,cmp);
st[top=1]=p[1];
for(R int i=2;i<=K;++i)
{
int tmp=LCA(p[i-1],p[i]);pt[i]=tmp,C=0;
while(top&&depth[st[top]]>depth[tmp]) AE(C,st[top]),AE(st[top],C),C=st[top--];
if(st[top]==tmp) AE(st[top],C),AE(C,st[top]);
if(depth[st[top]]<depth[tmp]) AE(tmp,C),AE(C,tmp),st[++top]=tmp;
st[++top]=p[i];
}
for(R int i=1;i<top;++i) AE(st[i],st[i+1]),AE(st[i+1],st[i]);
DP(1,0);
printf("%lld\n",f[1]);
for(R int i=1;i<=K;++i) v[p[i]]=false,headb[pt[i]]=headb[p[i]]=0;
tot=totb;
}
}