SSerxhs
2019-05-10 21:39:47
虚树,是对于一棵给定的节点数为
利用虚树,可以对于指定多组点集
一看到这种
先预处理整棵树 lca 和 dfs 序,接下来是对于每组询问的构造。
虚树的构建是一个增量算法,要首先将指定的这
虚树构建时会开一个栈
考虑如何加入一个新的节点
这种情况中,
这是原树上的情况。这时,“......”指代的那些节点以及
那我们不断弹出
注意弹完时可能
插入完所有点之后要完全回溯,也就是把栈内节点都弹出,也要连
代码如下,实现起来有一些差别
inline void ins(int x)
{
if (tp==0)
{
st[tp=1]=x;
return;
}
ance=lca(st[tp],x);
while ((tp>1)&&(dep[ance]<dep[st[tp-1]]))
{
add(st[tp-1],st[tp]);
--tp;
}
if (dep[ance]<dep[st[tp]]) add(ance,st[tp--]);
if ((!tp)||(st[tp]!=ance)) st[++tp]=ance;
st[++tp]=x;
}
对于任意指定两点
每个指定点进栈出栈一次,这部分
以例题为例介绍虚树的使用。首先特判掉无解情况(即一个点和他的父亲都被指定)。构造好虚树后,我们给真正被指定的点的 siz 设置成
以下所说的节点
对于一个被指定的点
对于一个未被指定的点
事实上难点完全在于建虚树,后面部分非常容易想到。
算法总复杂度
注意在每组询问最后一遍
完整代码如下
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
const int N=1e5+2,M=2e5+2;
int a[N],lj[M],nxt[M],fir[N],dfn[N],top[N],hc[N],siz[N],f[N],dep[N],st[N];
int n,m,q,i,x,y,c,bs,tp,ance,ans;
inline void read(int &x)
{
c=getchar();
while ((c<48)||(c>57)) c=getchar();
x=c^48;c=getchar();
while ((c>=48)&&(c<=57))
{
x=x*10+(c^48);
c=getchar();
}
}
inline void add(int x,int y)
{
lj[++bs]=y;
nxt[bs]=fir[x];
fir[x]=bs;
}
void dfs1(int x)
{
siz[x]=1;
int i;
for (i=fir[x];i;i=nxt[i]) if (lj[i]!=f[x])
{
dep[lj[i]]=dep[f[lj[i]]=x]+1;
dfs1(lj[i]);
siz[x]+=siz[lj[i]];
if (siz[hc[x]]<siz[lj[i]]) hc[x]=lj[i];
}
}
void dfs2(int x)
{
dfn[x]=++bs;
if (hc[x])
{
int i;
top[hc[x]]=top[x];
dfs2(hc[x]);
for (i=fir[x];i;i=nxt[i]) if ((lj[i]!=f[x])&&(lj[i]!=hc[x])) dfs2(top[lj[i]]=lj[i]);
}
}
inline int lca(int x,int y)
{
while (top[x]!=top[y]) if (dep[top[x]]>dep[top[y]]) x=f[top[x]]; else y=f[top[y]];
if (dep[x]<dep[y]) return x; return y;
}
void qs(int l,int r)//按照dfs序排序
{
int i=l,j=r,m=dfn[a[l+r>>1]];
while (i<=j)
{
while (dfn[a[i]]<m) ++i;
while (dfn[a[j]]>m) --j;
if (i<=j) swap(a[i++],a[j--]);
}
if (i<r) qs(i,r);
if (l<j) qs(l,j);
}
inline void ins(int x)
{
if (tp==0)
{
st[tp=1]=x;
return;
}
ance=lca(st[tp],x);//相当于z
while ((tp>1)&&(dep[ance]<dep[st[tp-1]]))
{
add(st[tp-1],st[tp]);
--tp;
}
if (dep[ance]<dep[st[tp]]) add(ance,st[tp--]);
if ((!tp)||(st[tp]!=ance)) st[++tp]=ance;
st[++tp]=x;
}//增量构建
void dfs3(int x)
{
int i;
if (siz[x]) for (i=fir[x];i;i=nxt[i])
{
dfs3(lj[i]);
if (siz[lj[i]])
{
siz[lj[i]]=0;
++ans;
}
}
else
{
for (i=fir[x];i;i=nxt[i])
{
dfs3(lj[i]);
siz[x]+=siz[lj[i]];
siz[lj[i]]=0;
}
if (siz[x]>1)
{
++ans;siz[x]=0;
}
}
fir[x]=0;//这里清空
}//对每组询问的解决
int main()
{
read(n);
for (i=1;i<n;i++)
{
read(x);read(y);
add(x,y);add(y,x);
}
bs=0;
dfs1(dep[1]=1);
dfs2(top[1]=1);
memset(fir+1,0,n<<2);
memset(siz+1,0,n<<2);
read(q);
bs=0;
while (q--)
{
x=1;
read(m);
for (i=1;i<=m;i++)
{
read(a[i]);
siz[a[i]]=1;
}
for (i=1;i<=m;i++) if (siz[f[a[i]]])
{
puts("-1");
x=0;
break;
}//特判无解
if (!x)
{
while (m) siz[a[m--]]=0;//清空打过的标记
continue;
}
ans=0;
qs(1,m);
if (a[1]!=1) st[tp=1]=1;//先行添加根节点
for (i=1;i<=m;i++) ins(a[i]);
if (tp) while (--tp) add(st[tp],st[tp+1]);//回溯
dfs3(1);
siz[1]=bs=0;
printf("%d\n",ans);
}
}
[HEOI2014]大工程
很裸,注意分类讨论即可。注意 dfs 要同时预处理最值和次值,类似树形 dp 处理树的直径。
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
const int N=1e6+2,M=2e6+2,inf=1e9;
typedef long long ll;
ll ans1;
int lj[M],nxt[M],fir[N],siz[N],top[N],dfn[N],hc[N],f[N],st[N],a[N],dep[N],dp[N][5],lc[N],dc[N];
int n,m,i,j,x,y,c,bs,tp,q,ance,len,ans2,ans3;
bool ed[N];
inline void read(int &x)
{
c=getchar();
while ((c<48)||(c>57)) c=getchar();
x=c^48;c=getchar();
while ((c>=48)&&(c<=57))
{
x=x*10+(c^48);
c=getchar();
}
}
inline void add(int x,int y)
{
lj[++bs]=y;
nxt[bs]=fir[x];
fir[x]=bs;
}
void dfs1(int x)
{
int i;
siz[x]=1;
for (i=fir[x];i;i=nxt[i]) if (lj[i]!=f[x])
{
dep[lj[i]]=dep[f[lj[i]]=x]+1;
dfs1(lj[i]);
siz[x]+=siz[lj[i]];
if (siz[lj[i]]>siz[hc[x]]) hc[x]=lj[i];
}
}
void dfs2(int x)
{
dfn[x]=++bs;
if (hc[x])
{
int i;
top[hc[x]]=top[x];
dfs2(hc[x]);
for (i=fir[x];i;i=nxt[i]) if ((lj[i]!=f[x])&&(lj[i]!=hc[x])) dfs2(top[lj[i]]=lj[i]);
}
}
inline int lca(int x,int y)
{
while (top[x]!=top[y]) if (dep[top[x]]<dep[top[y]]) y=f[top[y]]; else x=f[top[x]];
if (dep[x]<dep[y]) return x;
return y;
}
void qs(int l,int r)
{
int i=l,j=r,m=dfn[a[l+r>>1]];
while (i<=j)
{
while (dfn[a[i]]<m) ++i;
while (dfn[a[j]]>m) --j;
if (i<=j) swap(a[i++],a[j--]);
}
if (i<r) qs(i,r);
if (l<j) qs(l,j);
}
inline void ins(int x)
{
if (!tp)
{
st[tp=1]=x;
return;
}
ance=lca(st[tp],x);
while ((tp>1)&&(dep[st[tp-1]]>dep[ance]))
{
add(st[tp-1],st[tp]);
--tp;
}
if (dep[st[tp]]>dep[ance]) add(ance,st[tp--]);
if ((!tp)||(st[tp]!=ance)) st[++tp]=ance;
st[++tp]=x;
}
void dfs3(int x)//0 1短/2 3长/0 2最值/1 3次值
{
dp[x][0]=((siz[x]=ed[x])^1)*(dp[x][1]=inf);
dp[x][2]=(ed[x]^1)*(dp[x][3]=-inf);lc[x]=dc[x]=0;
int i;
for (i=fir[x];i;i=nxt[i])
{
dfs3(lj[i]);
siz[x]+=siz[lj[i]];
ans1+=(ll)siz[lj[i]]*(len=dep[lj[i]]-dep[x])*(m-siz[lj[i]]);
if (dp[x][0]>dp[lj[i]][0]+len)
{
if (dp[x][0]<dp[x][1]) dp[x][1]=dp[x][0];
dp[x][0]=dp[dc[x]=lj[i]][0]+len;
}
else dp[x][1]=min(dp[x][1],dp[lj[i]][0]+len);
if (dp[x][2]<dp[lj[i]][2]+len)
{
if (dp[x][2]>dp[x][3]) dp[x][3]=dp[x][2];
dp[x][2]=dp[lc[x]=lj[i]][2]+len;
}
else dp[x][3]=max(dp[x][3],dp[lj[i]][2]+len);
}
ans2=min(ans2,dp[x][0]+dp[x][1]);
ans3=max(ans3,dp[x][2]+dp[x][3]);
fir[x]=0;
ed[x]=0;
}
int main()
{
read(n);
for (i=1;i<n;i++)
{
read(x);read(y);
add(x,y);add(y,x);
}
read(q);
bs=0;
dfs1(dep[1]=1);
dfs2(top[1]=1);
bs=0;
memset(fir+1,0,n<<2);
while (q--)
{
read(m);
for (i=1;i<=m;i++)
{
read(a[i]);
ed[a[i]]=1;
}
qs(1,m);
if (a[1]!=1) st[tp=1]=1;
for (i=1;i<=m;i++) ins(a[i]);
while (--tp) add(st[tp],st[tp+1]);
ans1=ans3=0;ans2=inf;
dfs3(1);
printf("%lld %d %d\n",ans1,ans2,ans3);
}
}
SvT
后缀树+虚树,计算虚树上每条边贡献。据说也有 SA 解法。曾经想过把这题改一改再套一个点分治&树状数组
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
typedef long long ll;
const int S=5e5+2,N=1e6+2;
int c[N][27],dep[N],f[N],t[N],zd[N],lj[N],nxt[N],fir[N],len[N],siz[N],fa[N],s[S];
int top[N],hc[N],dfn[N],dy[N],st[N],a[N];
int n,m,q,i,j,x,ds=1,point=1,ad,r,edge,remain,bs,fbs,cc,tp,ance;
ll ans;
inline void read(int &x)
{
cc=getchar();
while ((cc<48)||(cc>57)) cc=getchar();
x=cc^48;cc=getchar();
while ((cc>=48)&&(cc<=57))
{
x=x*10+(cc^48);
cc=getchar();
}
}
inline void xadd(int x,int y)
{
lj[++bs]=y;
nxt[bs]=fir[x];
fir[x]=bs;
}
inline void add(int x,int y)
{
lj[++fbs]=y;
nxt[fbs]=fir[x];
fir[x]=fbs;
}
inline void add(int x,int y,int z)
{
lj[++fbs]=y;
len[bs]=z;
nxt[fbs]=fir[x];
fir[x]=bs;
}
inline void add(int a,int b,int cc,int d)
{
c[a][s[cc]]=++bs;
f[bs]=cc;t[bs]=d;
zd[bs]=b;
}
void dfs1(int x)
{
siz[x]=1;
if (!fir[x]) {dy[n-dep[x]+1]=x;return;}
int i;
for (i=fir[x];i;i=nxt[i])
{
dep[lj[i]]=dep[f[lj[i]]=x]+len[i];
dfs1(lj[i]);
siz[x]+=siz[lj[i]];
if (siz[lj[i]]>siz[hc[x]]) hc[x]=lj[i];
}
}
void dfs2(int x)
{
dfn[x]=++bs;
if (hc[x])
{
top[hc[x]]=top[x];
dfs2(hc[x]);
int i;
for (i=fir[x];i;i=nxt[i]) if (lj[i]!=hc[x]) dfs2(top[lj[i]]=lj[i]);
}
}
bool cmp(int x,int y)
{
return dfn[x]<dfn[y];
}
inline int lca(register int x,register int y)
{
while (top[x]!=top[y]) if (dep[top[x]]<dep[top[y]]) y=f[top[y]]; else x=f[top[x]];
if (dep[x]<dep[y]) return x; return y;
}
inline void ins(int x)
{
if (!tp) {st[tp=1]=x;return;}
ance=lca(st[tp],x);
while ((tp>1)&&(dep[st[tp-1]]>dep[ance]))
{
xadd(st[tp-1],st[tp]);
--tp;
}
if (dep[st[tp]]>dep[ance]) xadd(ance,st[tp--]);
if ((!tp)||(st[tp]!=ance)) st[++tp]=ance;
st[++tp]=x;
}
void dfs3(int x)
{
int i;
for (i=fir[x];i;i=nxt[i])
{
dfs3(lj[i]);ans+=(ll)siz[x]*siz[lj[i]]*dep[x];
siz[x]+=siz[lj[i]];
}
}
void dfs4(int x)
{
int i;
for (i=fir[x];i;i=nxt[i]) dfs4(lj[i]);
siz[x]=fir[x]=0;
}
int main()
{
read(n);read(q);
cc=getchar();
while ((cc<'a')||(cc>'z')) cc=getchar();
s[1]=cc-97;
for (i=2;i<=n;i++) s[i]=getchar()-97;fa[1]=1;
s[++n]=26;
for (i=1;i<=n;i++)
{
ad=0;++remain;
while (remain)
{
if (r==0) edge=i;
if ((j=c[point][s[edge]])==0)
{
fa[ad]=point;
fa[++ds]=1;
add(ad=point,ds,edge,n);
add(point,s[edge]);
}
else
{
if ((t[j]!=n)&&(t[j]-f[j]+1<=r))
{
r-=t[j]-f[j]+1;
edge+=t[j]-f[j]+1;
point=zd[j];
continue;
}
if (s[i]==s[f[j]+r])
{
++r;fa[ad]=point;break;
}
fa[fa[ad]=++ds]=1;add(ad=ds,zd[j],f[j]+r,t[j]);
add(ds,s[f[j]+r]);zd[j]=ds;t[j]=f[j]+r-1;
add(ds,s[i]);fa[++ds]=1;add(ds-1,ds,i,n);
}
--remain;
if ((r)&&(point==1))
{
--r;
edge=i-remain+1;
} else point=fa[point];
}
}//ukk后缀树,sam的可以按照自己板子来建
for (i=1;i<=ds;i++) for (j=fir[i];j;j=nxt[j])
{
x=c[i][lj[j]];
lj[j]=zd[x];
len[j]=t[x]-f[x]+1;
}
memset(f+1,0,bs<<2);bs=0;
dfs1(1);dfs2(top[1]=1);
memset(siz+1,0,ds<<2);
memset(fir+1,0,ds<<2);bs=0;
while (q--)
{
read(m);ans=bs=0;
for (i=1;i<=m;i++)
{
read(a[i]);siz[a[i]=dy[a[i]]]=1;
}
sort(a+1,a+m+1,cmp);if (a[1]!=1) ins(1);
for (i=1;i<=m;i++) if ((i==1)||(a[i]!=a[i-1])) ins(a[i]);
while (--tp) xadd(st[tp],st[tp+1]);
dfs3(1);dfs4(1);printf("%lld\n",ans);
}
}
[HNOI2014]世界树
[SDOI2011]消耗战