• 虚树


    虚树

    干啥用的呢?看道例题

    P2495 [SDOI2011]消耗战

    简要题意

    一棵(n)个节点的树,边有边权

    (m)次询问,每次询问有(k)个关键点,要断掉一些边(花费为边权),使得(1)不能到达任何关键点且花费最小

    (sum k le 5 imes 10^5)

    先考虑一次询问的情况

    树形DP

    (dp[u])表示(u)及其子树全都与(1)断开所需要的最小花费,设(mn[u])表示(u)(1)的路径上的最小值

    对于关键点:必须把自己到根的路径断开一条,即(dp[u]=mn[u])

    对于其他点,有两种情况

    • 自己到根节点的最小值
    • 自己所有的有关键节点的儿子都断开

    (dp[u]=min(sumlimits_{vin to[u]}dp[v],mn[u]))

    但是这样对于多次询问T的起飞

    再考虑多次询问

    很明显有贡献的点并不是那么多

    那么就要把这些有贡献的点找出来重新建树,这样复杂度就是(sum k)

    理性理解一下:有贡献的点只能是关键点和关键点两两的LCA

    那么怎么去找这些LCA呢

    首先我们维护一条最右链,用一个栈存储。这样的好处是:很多点的LCA是相同的,这样大大减少找到所有LCA所用时间

    • 第一种情况:自己是链顶的儿子,直接插入栈顶即可

    • 第二种情况:自己不是链顶的儿子

    把所有深度大于LCA的点都弹出,然后把LCA和自己入栈。出栈的时候建边,注意,离LCA最近的那个点(图中的(stac[top-1])要与LCA连边,不然建出来的树就不满足父子关系了)

    注意LCA若已经在链上就不要多次插入

    最后把留在链中的点都弹出(边弹边建边)

    还有一个细节:在栈底先插入一个(1),这样既保证(1)一直不会被弹出((dep[1])是最小的),这样循环不会有边界问题;同时也保证了(1)一定有连边

    找LCA可以(O(log n)),当然(O(nlog n))预处理(O(1))查询更好

    int stac[N],top;
    inline void push(int val){stac[++top]=val;}
    inline void pop(){
    	g2.adde(stac[top],stac[top-1],mn[stac[top]]);//边出栈边连边
    	--top;
    }
    inline bool cmp(int a,int b){return id[a]<id[b];}
    void build(){
    	sort(node+1,node+1+k,cmp);//排序
    	top=0;push(1);
    	for(int i=1;i<=k;++i){
    		int lca=LCA(node[i],stac[top]);
    		if(lca==stac[top]){push(node[i]);continue;}//是栈顶的儿子,直接插入
    		while(dep[stac[top-1]]>dep[lca])pop();
    		g2.adde(stac[top],lca,mn[stac[top]]);--top;//连向LCA
    		if(lca!=stac[top])push(lca);
    		push(node[i]);
    	}
    	while(top>1)pop();
    }
    

    这里排序是按(dfs)序排序,这样保证了最右链

    剩下的就是简单的树形DP了

    直接上代码

    long long dp[N];
    bool is_node[N];//是否是关键节点
    void dfs2(int u,int fat,long long pre){
    	long long sum=0;dp[u]=0;
    	for(int i=g2.head[u],v;i;i=g2.e[i].next){
    		v=g2.e[i].to;if(v==fat)continue;
    		dfs2(v,u,g2.e[i].len);sum+=dp[v];
    	}
    	g2.head[u]=0;
    	if(u==1)dp[u]=sum;
    	else if(is_node[u])dp[u]=pre;
    	else dp[u]=min(sum,pre);
    }
    

    还要注意建边之后清空必须一个一个清,不然退化成(O(n))

    这道题的完整代码

    const int N=3e5+5,LOG2=21;
    struct G{
    	struct Edge{int to,next,len;}e[N<<1];
    	int head[N],cnt;
    	inline void add(int u,int v,int w){e[++cnt].next=head[u];head[u]=cnt;e[cnt].len=w;e[cnt].to=v;}
    	inline void adde(int u,int v,int w){add(u,v,w);add(v,u,w);}
    }g1,g2;
    int Log2[N<<1],n;
    int mn[N]/*根到u的最小值*/,dep[N],st[N<<1][LOG2],id[N],tot;
    int m,node[N],k;
    inline int minn(int a,int b){return (dep[a]<dep[b])?a:b;}
    void dfs1(int u,int fat){
    	st[++tot][0]=u;id[u]=tot;dep[u]=dep[fat]+1;
    	for(int i=g1.head[u],v;i;i=g1.e[i].next){
    		v=g1.e[i].to;if(v==fat)continue;
    		mn[v]=min(mn[u],g1.e[i].len);
    		dfs1(v,u);st[++tot][0]=u;
    	}
    }
    inline void pre_work(){
    	dfs1(1,0);
    	for(int i=1;i<=tot;++i)Log2[i]=Log2[i-1]+(1<<Log2[i-1]==i);
    	for(int i=1;i<Log2[tot];++i)//ST表O(1)求LCA
    		for(int j=1;j+(1<<i)<=tot;++j)
    			st[j][i]=minn(st[j][i-1],st[j+(1<<(i-1))][i-1]);
    }
    inline int LCA(int x,int y){
    	if(x==y)return x;
    	if(id[x]>id[y])swap(x,y);
    	x=id[x],y=id[y];int len=Log2[y-x+1]-1;
    	return minn(st[x][len],st[y-(1<<len)+1][len]);
    }
    int stac[N],top;
    inline void push(int val){stac[++top]=val;}
    inline void pop(){
    	g2.adde(stac[top],stac[top-1],mn[stac[top]]);//边出栈边连边
    	--top;
    }
    inline bool cmp(int a,int b){return id[a]<id[b];}
    void build(){
    	sort(node+1,node+1+k,cmp);//排序
    	top=0;push(1);
    	for(int i=1;i<=k;++i){
    		int lca=LCA(node[i],stac[top]);
    		if(lca==stac[top]){push(node[i]);continue;}//是栈顶的儿子,直接插入
    		while(dep[stac[top-1]]>dep[lca])pop();
    		g2.adde(stac[top],lca,mn[stac[top]]);--top;//连向LCA
    		if(lca!=stac[top])push(lca);
    		push(node[i]);
    	}
    	while(top>1)pop();
    }
    long long dp[N];
    bool is_node[N];
    void dfs2(int u,int fat,long long pre){
    	long long sum=0;dp[u]=0;
    	for(int i=g2.head[u],v;i;i=g2.e[i].next){
    		v=g2.e[i].to;if(v==fat)continue;
    		dfs2(v,u,g2.e[i].len);sum+=dp[v];
    	}
    	g2.head[u]=0;
    	if(u==1)dp[u]=sum;
    	else if(is_node[u])dp[u]=pre;
    	else dp[u]=min(sum,pre);
    }
    int main(){
    	n=read();
    	memset(mn,0x3f,sizeof(mn));
    	for(int i=1,u,v,w;i<n;++i){u=read();v=read();w=read();g1.adde(u,v,w);}
    	pre_work();m=read();
    	while(m--){
    		k=read();for(int i=1;i<=k;++i)node[i]=read(),is_node[node[i]]=1;
    		build();	dfs2(1,0,0);
    		for(int i=1;i<=k;++i)is_node[node[i]]=0;g2.cnt=0;
    		printf("%lld
    ",dp[1]);
    	}
    	return 0;
    }
    
  • 相关阅读:
    树形DP新识
    HDU3652 B-number 数位DP第二题
    HDU3555 Bomb 数位DP第一题
    数位DP新识
    Codeforces Round #371 & HihoCoder1529【玄学】
    hihocoder1618 单词接龙
    后缀数组 逐步探索
    HDU2157 How many ways矩阵再识
    阿里云安全中心:自动化安全闭环实现全方位默认安全防护
    趣谈预留实例券,一文搞懂云上省钱最新玩法
  • 原文地址:https://www.cnblogs.com/harryzhr/p/14284587.html
Copyright © 2020-2023  润新知