标签:世界 sign 一个 pen printf back 树形dp -- turn
我记得我学过虚树啊 除了做过的题目有提交的痕迹 脑子空空如也。
今天一定要复习好虚树 我没剩多少时间了。
1.虚树是干嘛的?
对于一道题目 我们发现其每次询问树上的一些点集的某种定义下的答案 通常我们不需要再次遍历整棵树来寻找答案 可以利用题目中给出的这些点集建立一颗不存在的树 即称虚树。
2.如何构建
我们可以发现 如果单纯的把点插到一颗不存在的树上很好插入 但是我们不一定能维护好类似于原本树的形态。所以这个时候我们需要借助两两点之间的LCA来帮忙构建这棵树 维护好这棵树的基本形态。
3.复杂度的保证:由于是询问中的点集 我们可以将其按原本的dfs序排序 然后按顺序插入到我们的虚树当中去 并两两点求一波LCA
可以发现 LCA可以O(1)也可以logn 由于时间复杂度的瓶颈在于排序 所以总复杂度为点集大小K*logn.
4.应用
大多数题目都是让我们构建出虚树然后在虚树上搞点事情 如树形dp 点分治 线段树优化建图什么的。
总之这个技巧还是挺重要的。
虚树构建的过程:
虚树的构建依赖于单调栈,我们利用单调栈来维护 根到某个节点的一条笔直的链 这样我们就可以把整个基础的树的形态给完整的演绎出来。
想着 单调栈维护的是什么东西+画图思考 就可以得到一个虚树的构建过程了 一般这样的话就不容易写错了。
为什么要在弹栈的时候建边? 由于有些点深度小但是是后来以LCA的形式加入的 如果在某个点一开始就加入那么显然后面来一个LCA的话建边会错乱。
由于两两点之间的LCA只有一个 所以我们构建的虚树的空间复杂度也是询问点点集的复杂度。
code:
inline void insert(int x)
{
if(s[top]==1){s[++top]=x;return;}
int lca=LCA(s[top],x);
if(s[top]==lca){s[++top]=x;return;}
while(top>1&&dfn[s[top-1]]>=dfn[lca])
{
ADD(s[top-1],s[top]);
--top;
}
if(s[top]!=lca)
{
ADD(lca,s[top]);
s[top]=lca;
}
s[++top]=x;
}
int main()
{
sort(w+1,w+1+n,cmp);
s[top=1]=1;
rep(1,n,i)if(w[i]!=1)insert(w[i]);
while(top>1)ADD(s[top-1],s[top]),--top;
}
值得注意的是建的边可以是单向的 如果双向边没啥大用的话...
LINK:luogu2495SDOI2011消耗战
这道题目 初看是一道比较裸的树形dp 但是每次给出了关键点了 所以我们直接构建虚树在虚树上dp即可。
值得一提的是我们很容易发现 两个点同时为关键点且拥有祖先关系 那么 其中的一个点可以不要 因为祖先是一定要被割断的 割断的同时儿子也一定被隔断了...
所以我们在构建虚树的时候 可以把s[top]==lca时 不把x加入栈中 因为s[top]此时一定是关键点 且s[top]还是x的祖先。
估计是边权范围假了 INF 1e9 wa了
const int MAXN=250010;
int n,m,k,len,T,cnt,top;
int fa[MAXN][20],d[MAXN],dfn[MAXN],s[MAXN],Log[MAXN],a[MAXN];
int lin[MAXN],ver[MAXN<<1],nex[MAXN<<1];
ll f[MAXN],mx[MAXN],e[MAXN<<1];vector<int>g[MAXN];
inline int cmp(int a,int b){return dfn[a]<dfn[b];}
inline void add(int x,int y,int z)
{
ver[++len]=y;
nex[len]=lin[x];
lin[x]=len;
e[len]=z;
}
inline void dfs(int x,int father)
{
d[x]=d[father]+1;
fa[x][0]=father;dfn[x]=++cnt;
rep(1,Log[d[x]],i)fa[x][i]=fa[fa[x][i-1]][i-1];
for(int i=lin[x];i;i=nex[i])
{
int tn=ver[i];
if(tn==father)continue;
mx[tn]=min(mx[x],e[i]);
dfs(tn,x);
}
}
inline void add(int x,int y){g[x].push_back(y);}
inline int LCA(int x,int y)
{
if(d[x]<d[y])swap(x,y);
for(int i=Log[d[x]];i>=0;--i)
if(d[fa[x][i]]>=d[y])x=fa[x][i];
if(x==y)return x;
for(int i=Log[d[x]];i>=0;--i)
if(fa[x][i]!=fa[y][i])x=fa[x][i],y=fa[y][i];
return fa[x][0];
}
inline void insert(int x)
{
if(top==1){s[++top]=x;return;}
int lca=LCA(s[top],x);
if(lca==s[top])return;
while(top>1&&dfn[s[top-1]]>=dfn[lca])
{
add(s[top-1],s[top]);
--top;
}
if(s[top]!=lca)
{
add(lca,s[top]);
s[top]=lca;
}
s[++top]=x;
}
inline void dp(int x)
{
f[x]=mx[x];
if(!g[x].size())return;
ll sum=0;
for(unsigned int i=0;i<g[x].size();++i)
{
int tn=g[x][i];
dp(tn);
sum+=f[tn];
}
f[x]=min(f[x],sum);
g[x].clear();
}
int main()
{
freopen("1.in","r",stdin);
get(n);
rep(2,n,i)
{
int x,y,z;
get(x);get(y);get(z);
add(x,y,z);add(y,x,z);
Log[i]=Log[i>>1]+1;
}
mx[1]=INF;dfs(1,0);
get(m);
rep(1,m,i)
{
int k;get(k);s[top=1]=1;
rep(1,k,j)get(a[j]);
sort(a+1,a+1+k,cmp);
rep(1,k,j)insert(a[j]);
while(top>1)add(s[top-1],s[top]),--top;
dp(1);printf("%lld\n",f[1]);
}
return 0;
}
这道虚树的模板题写起来挺舒服的。。明天一定要把世界树给肝出来!
标签:世界 sign 一个 pen printf back 树形dp -- turn
原文地址:https://www.cnblogs.com/chdy/p/12489914.html