虚树
在给定树上给出一些关键点,要求构造一棵树,满足所有关键点都在这棵树上,且树的形态与关键点在原树上的形态不变:即本不是祖先/后辈关系的点成为祖先/后辈是不允许的,本来是祖先/后辈的点如果在这棵树上也得是祖先/后辈。
更通俗一点的话,就是把所有关键点找出来,把两两关键点的 LCA 找出来,再根据之前的祖先后辈关系来连边,构成一棵新树。这棵新树就是原树的虚树。
一般可以在虚树上进行 DP,前提是这个 DP 只跟在虚树上的点和其它一些好维护的东西有关,比如两点距离(虚树把一条链的中间缩了,但这条链上的一些信息可以保留在虚树上对应的边上)。
先讲虚树构建方法,然后分析时间复杂度,再讲例题。
给出关键点集合,已知原树,求这个集合构成的虚树。
暴力一点,枚举任意两点及其 LCA,都放到集合里,再去重,再暴力构树。但是这样太暴力了。
朴素一点,把原树 dfs 一遍,一个点如果只有一个子树内有关键点,且自己不是关键点,就被缩,否则就不被缩。这样时间复杂度也不够优秀。
再回到第一个暴力做法,想一下去重后这个集合有多大。结合第二个做法,即最多有多少个节点自身不是关键点,且不只有一个子树内有关键点。
考虑到,每有一个上述说到的不只有一个子树内有关键点,且自身不是管简单的点,就说明至少有 2 个关键点在这个点为根的子树内。反过来——每两个关键点会诞生一个 LCA(或没有),然后这两个关键点没用了,而这个 LCA 会成为新的关键点。最多可以诞生关键点数量 -1 个新点。即——虚树大小跟关键点数量线性相关。
那么可以通过某种方法不重不漏地找出这些点,即可不与原树大小挂钩(虽然预处理需要)。
维护从根到当前要加入的点的链,从感性上:从左到右,在同一条链上就从上到下地加入关键点,如果关键点在这条链下面,就挂在这下面,否则找出加入点到维护链的第一个相交的点(即为维护链链底和加入点在原树上的 LCA)。由于加入的顺序问题,左边的已经加入完了,把链上的点从下往上删直到链上没有比刚才求的 LCA 深度更低的点,再把 LCA 和新加入点挂在链上。而虚树建边,在每一对点从链上删除之后再加边。加入完之后把当前维护的这条链上的边也加上。
具体实现,可以用单调栈。
具体加入顺序:按照 dfs 序排序。
一种很厉害的求 LCA 算法:欧拉序上求区间深度最小值在树上的编号。欧拉序每经过一个点就记录,即使递归一个儿子再回来时也要记录。欧拉序的长度为点数的两倍。可以 ST 表求区间最小,x 和 y 的 LCA 即为 x 的第一次出现时的欧拉序到 y 的第一次出现时的欧拉序的区间里深度最浅的那个点。
具体代码:
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN=1e6+50;
int N,M;
struct Edge
{
int x,y,Next;
long long Len;
}e[MAXN<<1];
int elast[MAXN],trlast[MAXN],tot;
void Add(int x,int y,long long Len)
{
tot++;
e[tot].x=x;
e[tot].y=y;
e[tot].Len=Len;
e[tot].Next=elast[x];
elast[x]=tot;
}
void Addtr(int x,int y)
{
tot++;
e[tot].x=x;
e[tot].y=y;
e[tot].Next=trlast[x];
trlast[x]=tot;
}
int In[MAXN],Out[MAXN],CNT;
int depth[MAXN];
long long Min[MAXN];
int Back[MAXN];
bool cmp(int a,int b)
{
return In[a]<In[b];
}
struct STable
{
int Id,Val;
STable(){}
STable(int _Id,int _Val)
{
Id=_Id;
Val=_Val;
}
}ST[MAXN][21];
void dfs(int u,int fa)
{
In[u]=++CNT;
Back[CNT]=u;
depth[In[u]]=depth[In[fa]]+1;
ST[In[u]][0].Id=In[u];
ST[In[u]][0].Val=depth[In[u]];
for(int i=elast[u];i;i=e[i].Next)
{
int v=e[i].y;
if(v==fa)
continue;
Min[v]=min(Min[u],e[i].Len);
dfs(v,u);
ST[++CNT][0].Id=In[u];
ST[CNT][0].Val=depth[In[u]];
}
}
bool operator < (STable a,STable b)
{
return a.Val<b.Val;
}
bool operator > (STable a,STable b)
{
return a.Val>b.Val;
}
STable max(STable a,STable b)
{
return a>b?a:b;
}
STable min(STable a,STable b)
{
return a<b?a:b;
}
int Log2[MAXN];
int lowbit(int x)
{
return x&-x;
}
void Init(int N)
{
Log2[1]=0;
for(int i=2;i<=N;i++)
{
if(i==lowbit(i))
{
Log2[i]=Log2[i-1]+1;
}
else
{
Log2[i]=Log2[i-1];
}
}
}
void GetMinST(int N)
{
for(int i=N;i>=1;i--)
{
for(int j=1;i+(1<<j)-1<=N;j++)
{
ST[i][j]=min(ST[i][j-1],ST[i+(1<<j-1)][j-1]);
}
}
}
STable GetMin(int op,int l,int r)
{
int t=Log2[r-l+1];
return min(ST[l][t],ST[r-(1<<t)+1][t]);
}
int sta[MAXN],Top;
vector<int>Have;
void BuildTree(vector<int>Vec)
{
bool HaveRoot=false;
for(int j=0;j<Vec.size();j++)
{
if(Vec[j]==1)
{
HaveRoot=true;
break;
}
}
if(HaveRoot==false)
Vec.push_back(1);
sort(Vec.begin(),Vec.end(),cmp);
Top=0;
sta[++Top]=Vec[0];
int LCA=0;
for(int i=1;i<Vec.size();i++)
{
LCA=Back[GetMin(0,In[sta[Top]],In[Vec[i]]).Id];
if(LCA==sta[Top])
{
sta[++Top]=Vec[i];
}
else
{
while(Top>1&&depth[In[sta[Top-1]]]>=depth[In[LCA]])
{
Addtr(sta[Top-1],sta[Top]);
Top--;
}
if(sta[Top]!=LCA)
{
Addtr(LCA,sta[Top]);
Top--;
sta[++Top]=LCA;
}
sta[++Top]=Vec[i];
}
}
while(Top>1)
{
Addtr(sta[Top-1],sta[Top]);
Top--;
}
return;
}
vector<int>Vec;
long long f[MAXN];
int Color,Use[MAXN];
void dp(int u)
{
Have.push_back(u);
f[u]=0;
int Son=0;
for(int i=trlast[u];i;i=e[i].Next)
{
int v=e[i].y;
dp(v);
f[u]+=f[v];
Son++;
}
if(Use[u]==Color)
{
f[u]=Min[u];
return;
}
f[u]=min(f[u],Min[u]);
}
int main()
{
scanf("%d",&N);
for(int i=1;i<N;i++)
{
int x,y,Len;
scanf("%d%d%d",&x,&y,&Len);
Add(x,y,Len);
Add(y,x,Len);
}
Min[1]=1e18;
dfs(1,0);
Init(CNT);
GetMinST(CNT);
scanf("%d",&M);
while(M--)
{
Have.clear();
Color++;
int K;
scanf("%d",&K);
Vec.clear();
for(int i=1;i<=K;i++)
{
int x;
scanf("%d",&x);
Vec.push_back(x);
Use[x]=Color;
}
BuildTree(Vec);
dp(1);
printf("%lld\n",f[1]);
tot=0;
for(int i=0;i<Have.size();i++)
trlast[Have[i]]=0;
}
}
每次算完再清空的细节总是很多。也是写挂的常见错误点。
本文来自博客园,作者:0htoAi,转载请注明原文链接:https://www.cnblogs.com/0htoAi/p/16778808.html