• 【学习笔记】虚树复习记(BZOJ2286 SDOI2011 消耗战)


    想写战略游戏却想不起来虚树T^T

    所以就有了这篇复习记QwQ

    ——简介!——

    我们在处理树上问题的时候,dfs是一个常用手段,但是我们发现,如果一棵树上只有一部分关键点,每次dfs需要访问好多不是关键的点,就很浪费时间。所以虚树就被发明出来啦!看到一个非常好的解释,虚树就是通过简化树的形态来进行dfs从而加快效率。

    他处理的问题中很多都是 sum k_i <= 3e5这样的,其中k表示询问节点个数。

    建出的虚树中包含所有的关键点 和一些非关键点连接关键点来维护原树中关键点的相对形态

    说起来有一点玄学,所以我们直接来看怎么构造虚树的吧。

    前缀芝士

    1.DFS序

    2.倍增

    3.树形DP

    ——构造!——

    首先,我们将询问的关键点按照原树中的DFS序排序。

    我们用一个栈来辅助构造虚树。

    记住这几句话:压栈不连边,弹栈必连边。栈中一条链,深度是递增。【我好会编口诀啊】

    (是栈底到栈顶递增qwq)

    接下来我们来讨论一下3种情况。(这里配合画图食用口味更佳!)

    [x:当前要加入栈中的元素 y:栈顶的元素 lca:x,y的lca z:栈中栈顶下方的元素]

    1. lca 为 y

    说明x位于y子树内 直接把x压入栈内 表示x挂到位于y下方的链上。

    2. lca 既不是 x 也不是 y

    表示x,y分别存在于 lca 的两侧 分为两棵子树

    这时候 我们发现对于y子树应该是需要构造完 我们才可以把x压入栈内 不然的话就不是一条链了

    这时候我们继续讨论几种情况。

        (1)dfn[z]>dfn[lca] 说明y和z之间不会再出现新的节点 直接弹栈连边。

        (2)z==lca z就是lca 表示 y和z之间没有新的点 弹栈连边 并且退出循环。

        (3)dfn[z]<dfn[lca] 说明lca应该是作为新建节点 并且将来要和x连边[还是位于一条链] 那么将y弹栈连边,退出循环

    3. lca 为 x

    这种情况不会存在,因为我们按照dfs序排序了,所以x一定不会是y的祖先。

    还有一个很重要的地方!

    我们建虚树的时候 只是使用了一些关键节点,所以复杂度是低于O(n)的 如果清空数组使用了memset就是O(n)然后就萎啦

    所以一个小技巧就是自杀式遍历 或者存下使用的节点 删除即可

    ——例题——

    BZOJ2286

    直接建虚树 然后熟悉的树形dp 

    令f[i]表示 i子树内部的所有点和1断开的最小代价

    如果i是关键点 那么f[i]=dis[i] (1-i路径上的最小值)

    如果i不是关键点 那么 f[i]=min(dis[i],sum(f[v])) [v是i的儿子]

    然后就可以做啦~

    附代码。

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<cmath>
    #define inf 20021225
    #define ll long long
    #define mxn 2500100
    using namespace std;
    
    struct edge{int to,lt;ll v;}e[mxn<<1],p[mxn<<1];
    int in[mxn],cnt,cnp,ip[mxn],stk[mxn];
    int ff[mxn][20],dfn[mxn],tot;
    ll f[mxn],dis[mxn];int n,m,dep[mxn];
    void add(int x,int y)
    {
    	e[++cnt].to=y;e[cnt].lt=in[x];in[x]=cnt;
    }
    void app(int x,int y,ll v)
    {
    	p[++cnp].to=y;p[cnp].lt=ip[x];p[cnp].v=v;ip[x]=cnp;
    	p[++cnp].to=x;p[cnp].lt=ip[y];p[cnp].v=v;ip[y]=cnp;
    }
    void dfs(int x)
    {
    	dfn[x]=++tot;
    	for(int i=1;i<20;i++)
    		ff[x][i]=ff[ff[x][i-1]][i-1];
    	for(int i=ip[x];i;i=p[i].lt)
    	{
    		int y=p[i].to;if(dfn[y])	continue;
    		ff[y][0]=x;dis[y]=min(dis[x],p[i].v);
    		dep[y]=dep[x]+1;dfs(y);
    	}
    }
    int LCA(int x,int y)
    {
    	if(dep[x]<dep[y])	swap(x,y);
    	int len=dep[x]-dep[y];
    	for(int i=19;~i;i--)	if(len&(1<<i))	x=ff[x][i];
    	if(x==y)	return x;
    	for(int i=19;~i;i--)
    		if(ff[x][i]!=ff[y][i])
    			x=ff[x][i],y=ff[y][i];
    	return ff[x][0];
    }
    int poi[mxn];bool spc[mxn];
    bool cmp(int x,int y){return dfn[x]<dfn[y];}
    void insert(int x)
    {
    	if(!stk[0]){stk[++stk[0]]=x;return;}
    	int lca=LCA(stk[stk[0]],x);
    	if(lca!=stk[stk[0]])
    	while(stk[0]>1)
    	{
    		int z=stk[stk[0]-1];
    		if(dfn[z]>dfn[lca])
    			add(z,stk[stk[0]]),stk[0]--;
    		else if(z==lca)
    		{
    			add(z,stk[stk[0]]),stk[0]--;
    			break;
    		}
    		else if(dfn[z]<dfn[lca])
    		{
    			add(lca,stk[stk[0]]);
    			stk[stk[0]]=lca;break;
    		}	
    	}
    	if(stk[stk[0]]!=x)	stk[++stk[0]]=x;
    }
    void build(int q)
    {
    	sort(poi+1,poi+q+1,cmp);
    	cnt=0;stk[stk[0]=1]=1;
    	for(int i=1;i<=q;i++)
    		//if(poi[i]==1)	continue;
    		insert(poi[i]);
    	while(stk[0]>1)
    		add(stk[stk[0]-1],stk[stk[0]]),stk[0]--;
    }
    void dp(int x)
    {
    	f[x]=100000000000ll;ll sum=0;
    	for(int &i=in[x];i;i=e[i].lt)
    	{
    		int y=e[i].to;
    		dp(y);sum+=f[y];
    	}
    	if(spc[x])	f[x]=dis[x];
    	else	f[x]=min((ll)dis[x],sum);
    	spc[x]=0;
    }
    int main()
    {
    	int x,y,q;ll v;
    	scanf("%d",&n);
    	for(int i=1;i<n;i++)	scanf("%d%d%lld",&x,&y,&v),app(x,y,v);
    	scanf("%d",&m);
    	memset(dis,48,sizeof(dis));
    	dep[1]=1;dfs(1);
    	for(int i=1;i<=m;i++)
    	{
    		scanf("%d",&q);
    		for(int j=1;j<=q;j++)
    		{
    			scanf("%d",&x);
    			spc[x]=1;poi[j]=x;
    		}
    		build(q);dp(1);
    		printf("%lld
    ",f[1]);
    	}
    	return 0;
    }
  • 相关阅读:
    Windows下Goland的Terminal设置为Git Bash
    BoltDB简单使用教程
    Base64编码转换原理
    [区块链|非对称加密] 对数字证书(CA认证)原理的回顾
    [数据库锁机制] 深入理解乐观锁、悲观锁以及CAS乐观锁的实现机制原理分析
    升级mojave后的小问题解决
    ubuntu安装ssh服务记录
    dubbo+maven多模块项目单元测试
    sass与less
    (转)初识 Lucene
  • 原文地址:https://www.cnblogs.com/hanyuweining/p/10321924.html
Copyright © 2020-2023  润新知