bzoj 2286 虚树

调用了一下小猴子的思路。。。

概念:

        给出一棵树.
        每次询问选择一些点,求一些东西.这些东西的特点是,许多未选择的点可以通过某种方式剔除而不影响最终结果.
        于是就有了建虚树这个技巧.....
        我们可以用log级别的时间求出点对间的lca....
        那么,对于每个询问我们根据原树的信息重新建树,这棵树中要尽量少地包含未选择节点. 这棵树就叫做虚树.
        接下来所说的"树"均指虚树,原来那棵树叫做"原树".
        构建过程如下:
        按照原树的dfs序号(记为dfn)递增顺序遍历选择的节点. 每次遍历节点都把这个节点插到树上.
        首先虚树一定要有一个根. 随便扯一个不会成为询问点的点作根.(并不觉得是这样)
        维护一个栈,它表示在我们已经(用之前的那些点)构建完毕的虚树上,以最后一个插入的点为端点的DFS链.
        设最后插入的点为p(就是栈顶的点),当前遍历到的点为x.我们想把x插入到我们已经构建的树上去.
        求出lca(p,x),记为lca.有两种情况:
          1.p和x分立在lca的两棵子树下.
          2.lca是p.
          (为什么lca不能是x?
           因为如果lca是x,说明dfn(lca)=dfn(x)<dfn(a),而我们是按照dfs序号遍历的,于是dfn(a)<dfn(x),矛盾.)
        对于第二种情况,直接在栈中插入节点x即可,不要连接任何边(后面会说为什么).
        对于第一种情况,要仔细分析.
        我们是按照dfs序号遍历的(因为很重要所以多说几遍......),有dfn(x)>dfn(p)>dfn(lca).
        这说明什么呢? 说明一件很重要的事:我们已经把lca所引领的子树中,p所在的子树全部遍历完了!
          简略的证明:如果没有遍历完,那么肯定有一个未加入的点h,满足dfn(h)<dfn(x),
            我们按照dfs序号递增顺序遍历的话,应该把h加进来了才能考虑x.
        这样,我们就直接构建lca引领的,p所在的那个子树. 我们在退栈的时候构建子树.
        p所在的子树如果还有其它部分,它一定在之前就构建好了(所有退栈的点都已经被正确地连入树中了),就剩那条链.
        如何正确地把p到lca那部分连进去呢?
        设栈顶的节点为p,栈顶第二个节点为q.
        重复以下操作:
  如果dfn(q)>dfn(lca),可以直接连边q->p,然后退一次栈. --> while()!!! 
【 以q为根的子树在连q->p后构建完毕,接下来构建以lca为根的子树内x及其他dfs序较大的部分 】 
  如果dfn(q)=dfn(lca),说明q=lca,直接连边lca->p,[退一次栈]【此时子树已经构建完毕】.
   【 以p为根的子树在连q(lca)->p后构建完毕,接下来构建以q为根的子树内x及dfs序较大的部分 】 
如果dfn(q)<dfn(lca),说明lca被p与q夹在中间,此时连边lca->[p],退一次栈,再把lca压入栈.【此时子树构建完毕】.
【 以p为根的子树在连lca->p后构建完毕,lca为q连接p及x的交叉点需保留,接下来构建以lca为根的子树中x及dfs序较大的部分 】 
  如果不理解这样操作的缘由可以画画图.....
        最后,为了维护dfs链,要把x压入栈. 整个过程就是这样.....

【栈:维护右链】 

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
inline int read(void){
	int x=0,f=1; char ch=getchar();
	for(;ch<'0'||ch>'9';ch=getchar()) if(ch=='-') f=-1;
	for(;ch>='0'&&ch<='9';x=(x<<3)+(x<<1)+ch-'0',ch=getchar());
	return x*f;
}
typedef long long LL;
const LL maxn=500009;
LL n,m,tot,top,cnt,z;
LL head[maxn],q[maxn],dfn[maxn],c[maxn];
LL num[maxn],p[maxn],mi[maxn][29],f[maxn];
LL fa[maxn][29],st[maxn][29],pw[29];
bool b[maxn];
struct LJM{
	LL to,next,v;
}a[maxn];
inline void add(LL f,LL t,LL w){
	a[++tot].to=t; a[tot].v=w;
	a[tot].next=head[f]; head[f]=tot;
	a[++tot].to=f; a[tot].v=w;
	a[tot].next=head[t]; head[t].tot;
}
void dfs(LL x,LL ffa){
	dfn[x]=++cnt;
	num[cnt]=x;
	st[++z][0]=cnt;
	p[x]=z;
	for(LL i=head[x];i!=-1;i=a[i].next)
		if(a[i].to!=ffa)
			fa[a[i].to][0]=x,mi[a[i].to][0]=a[i].v,
			dfs(a[i].to,x),st[++z][0]=st[p[x]][0];
}
inline void poww(void){
	for(LL j=1;j<=20;j++)
		for(LL i=1;i<=n;i++)
			fa[i][j]=fa[fa[i][j-1]][j-1],
			mi[i][j]=min(mi[i][j-1],mi[fa[i][j-1]][j-1]);
}
inline void rmp(void){
	for(LL j=1;j<=20;j++)
		for(LL i=1;i<=z;i++)
			st[i][j]=min(st[i][j-1],st[i+pw[j-1]][j-1]);	//Look out to make three times larger memory
}
inline bool cmp(const LL&x,const LL&y){
	return dfn[x]<dfn[y];
}
inline LL findmin(LL x,LL tar){
	LL ans=1e9;
	for(LL i=20;i>=0;i--)
		if(dfn[fa[x][i]]>=dfn[tar])
			ans=min(ans,mi[x][i]),x=fa[x][i];
//	if(ans==1e9) puts("CNM");
	return ans;		
}
inline LL dp(LL x,LL fa){
	for(LL i=head[x];i!=-1;i=a[i].next)
		if(a[i].to!=fa){		//head[x]!=-1 !!!
			dp(a[i].to,x);
			f[x]+=b[a[i].to]?a[i].v:min(a[i].v,f[a[i].to]);		//!!!
		}
	if(!f[x]) f[x]=(LL)1e16;
}
inline void clear(LL x,LL fa){
	for(LL i=head[x];i!=-1;i=a[i].next)
		if(a[i].to!=fa) clear(a[i].to,x);
	head[x]=-1; b[x]=f[x]=0;
}
int main(int argc,char const *argv[]){
	pw[0]=1;
	for(LL i=1;i<=20;i++)
		pw[i]=pw[i-1]<<1;
	memset(head,-1,sizeof(head));
	n=read();
	for(LL i=1,fr,t,w;i<n;i++){
		fr=read(),t=read(),w=read();
		add(fr,t,w);
	}
	dfs(1,0); poww(); rmq(); m=read();
	memset(head,-1,sizeof(head));
	for(LL i=1,k;i<=m;i++){
		tot=0; top=0; k=read();
		for(LL j=1;j<=k;j++)
			c[j]=read(),b[c[j]]=1;
		sort(c+1,c+k+1,cmp);
		q[++top]=1; q[++top]=c[1];
		for(LL j=2;j<=k;j++){
			if(lca(c[j],q[top])==q[top]) q[++top]=c[j];
			else{
				while(dfn[q[top-1]]>dfn[lca(c[j],q[top])])	//while!!!
					add(q[top-1],q[top],findmin(q[top],q[top-1])),top--;
				if(dfn[q[top-1]]==dfn[lca(c[j],q[top])])
					add(q[top-1],q[top],findmin(q[top],q[top-1])),top--;
				else{
					q[top+1]=lca(c[j],q[top]);
					add(q[top+1],q[top],findmin(q[top],q[top+1]));
					q[top]=q[top+1];
				}
				q[++top]=c[j];
			}
		}
		while(top>1)
			add(q[top-1],q[top],findmin(q[top],q[top-1])),top--;
		dp(1,0); printf("%lld\n",f[1]); clear(1,0);
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值