概述
题目分析
首先,这题肯定要用虚树。
接下来,你会发现,使得 f ( u ) f(u) f(u)最大的 u u u,可能存在于三个位置:
- 虚树上
- 虚树上一个没有关键点的子树中
- 虚树上的一条边中
于是便分类讨论。
对于第一种情况,只需要在虚树上做DP求出虚树上的每个点 x x x到最近的关键点的距离 d t k p ( x ) dtkp(x) dtkp(x)即可(dist to key point)
对于第二种情况,我们对整棵树DP,得到每个点到子树中的点的最远距离 f i r ( x ) fir(x) fir(x),然后将它的儿子们按照fir从大到小排序。对于虚树上的每个点 x x x,找到第一个不在虚树上的儿子 y y y,用 f i r ( y ) + 1 + d t k p ( x ) fir(y)+1+dtkp(x) fir(y)+1+dtkp(x)更新答案。
对于第三种情况,比较麻烦。我们对整棵树DP的时候,还要得到一个 s e ( x ) se(x) se(x),表示 x x x不走能够造成 f i r ( x ) fir(x) fir(x)贡献的那棵子树,离最远点的距离。于是我们就可以预处理两个倍增数组: U ( j , i ) U(j,i) U(j,i)和 D ( j , i ) D(j,i) D(j,i)在 j = 0 j=0 j=0时的值。
U ( j , i ) U(j,i) U(j,i): i i i走到原树上的前 2 j 2^j 2j个祖先中的一个后,再往该祖先的一棵子树里走,用这样的策略可以走的最远距离。
D ( j , i ) D(j,i) D(j,i): i i i的原树上前 2 j 2^j 2j个祖先中的一个,往自己的子树里走,不经过子树 i i i可以走的最远距离。
不难发现,如果我们规定虚树上的一个点 x x x和它儿子 y y y中间边上的点,全部必须先走到 x x x再走到关键点,贡献可以通过 D D D算出来。而若全部必须先走到 y y y再走到关键点,贡献可以通过 U U U算出来。
通过 d t k p ( x ) dtkp(x) dtkp(x)和 d t k p ( y ) dtkp(y) dtkp(y)的值,我们就可以确定这条边从哪个位置开始划分,上面的点全走到 x x x再去关键点,下面的点全走到 y y y再去关键点,就能算出答案了。
复杂度 O ( n log n ) O(n log n) O(nlogn)
代码
#include<bits/stdc++.h>
using namespace std;
#define RI register int
int read() {
int q=0;char ch=' ';
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
return q;
}
const int N=100005,inf=0x3f3f3f3f;
int n,Q,tot,ans,qcnt,tim,top,rub_top;
int h[N],ne[N<<1],to[N<<1],pos[N],fir[N],se[N];
int f[17][N],U[17][N],D[17][N],dep[N],bin[17];
int q[N],is[N],st[N],dtkp[N],rub[N],cantGo[N];
vector<int> son[N];
void add(int x,int y) {to[++tot]=y,ne[tot]=h[x],h[x]=tot;}
bool cmpson(int x,int y) {return fir[x]>fir[y];}
void dfs(int x,int las) {
f[0][x]=las,dep[x]=dep[las]+1,pos[x]=++tim;
for(RI i=h[x];i;i=ne[i]) {
if(to[i]==las) continue;
dfs(to[i],x),son[x].push_back(to[i]);
if(fir[to[i]]+1>fir[x]) se[x]=fir[x],fir[x]=fir[to[i]]+1;
else if(fir[to[i]]+1>se[x]) se[x]=fir[to[i]]+1;
}
sort(son[x].begin(),son[x].end(),cmpson);
for(RI i=h[x];i;i=ne[i]) {
if(to[i]==las) continue;
if(fir[to[i]]+1==fir[x]) U[0][to[i]]=se[x]+1,D[0][to[i]]=se[x];
else U[0][to[i]]=fir[x]+1,D[0][to[i]]=fir[x];
}
}
void prework() {
bin[0]=1;for(RI i=1;i<=16;++i) bin[i]=bin[i-1]<<1;
for(RI j=1;j<=16;++j)
for(RI i=1;i<=n;++i) {
f[j][i]=f[j-1][f[j-1][i]];
U[j][i]=max(U[j-1][i],U[j-1][f[j-1][i]]+bin[j-1]);
D[j][i]=max(D[j-1][i]+bin[j-1],D[j-1][f[j-1][i]]);
}
}
int lca(int x,int y) {
if(dep[x]<dep[y]) swap(x,y);
for(RI i=16;i>=0;--i) if(dep[f[i][x]]>=dep[y]) x=f[i][x];
if(x==y) return x;
for(RI i=16;i>=0;--i) if(f[i][x]!=f[i][y]) x=f[i][x],y=f[i][y];
return f[0][x];
}
int goup(int x,int d) {
for(RI i=16;i>=0;--i) if(d&bin[i]) x=f[i][x];
return x;
}
int getD(int x,int d) {
int re=0;
for(RI i=16;i>=0;--i)
if(d&bin[i]) d^=bin[i],re=max(re,d+D[i][x]),x=f[i][x];
return re;
}
int getU(int x,int d) {
int re=0,kdist=0;
for(RI i=16;i>=0;--i)
if(d&bin[i]) re=max(re,kdist+U[i][x]),kdist|=bin[i],x=f[i][x];
return re;
}
bool cmp(int x,int y) {return pos[x]<pos[y];}
void DP1(int x) {
if(is[x]) dtkp[x]=0; else dtkp[x]=inf;
for(RI i=h[x];i;i=ne[i]) {
DP1(to[i]);
dtkp[x]=min(dtkp[x],dtkp[to[i]]+dep[to[i]]-dep[x]);
int k=goup(to[i],dep[to[i]]-dep[x]-1);
cantGo[k]=1,rub[++rub_top]=k;
}
}
void DP2(int x) {
for(RI i=h[x];i;i=ne[i]) {
dtkp[to[i]]=min(dtkp[to[i]],dtkp[x]+dep[to[i]]-dep[x]);
DP2(to[i]);
}
}
void getans(int x) {
ans=max(ans,dtkp[x]);//虚树上
for(RI i=h[x];i;i=ne[i]) {
int d=dep[to[i]]-dep[x];
if(d>1) {//虚树边上
if(dtkp[to[i]]-dtkp[x]==d)
ans=max(ans,getD(to[i],d-1)+1+dtkp[x]);
else if(dtkp[x]-dtkp[to[i]]==d)
ans=max(ans,getU(to[i],d-1)+dtkp[to[i]]);
else {
int kd=(dep[to[i]]-dep[x]+dtkp[x]-dtkp[to[i]])/2;
int k=goup(to[i],kd);
if(f[0][k]!=x) ans=max(ans,getD(k,dep[k]-dep[x]-1)+1+dtkp[x]);
ans=max(ans,getU(to[i],kd)+dtkp[to[i]]);
}
}
getans(to[i]);
}
int sz=son[x].size();//无关键点的子树中
for(RI i=0;i<sz;++i)
if(!cantGo[son[x][i]]) {ans=max(ans,fir[son[x][i]]+1+dtkp[x]);break;}
h[x]=0;
}
void work() {
ans=top=tot=rub_top=0;
sort(q+1,q+1+qcnt,cmp);
if(!is[1]) st[++top]=1;
for(RI i=1;i<=qcnt;++i) {
int o=0,x=q[i];
while(top) {
o=lca(x,st[top]);
if(top>1&&dep[st[top-1]]>dep[o]) add(st[top-1],st[top]),--top;
else if(dep[st[top]]>dep[o]) {add(o,st[top]),--top;break;}
else break;
}
if(st[top]!=o) st[++top]=o;
st[++top]=x;
}
while(top>1) add(st[top-1],st[top]),--top;
DP1(1),DP2(1),getans(1);
for(RI i=1;i<=qcnt;++i) is[q[i]]=0;
for(RI i=1;i<=rub_top;++i) cantGo[rub[i]]=0;
}
int main()
{
int x,y;
n=read(),Q=read();
for(RI i=1;i<n;++i) x=read(),y=read(),add(x,y),add(y,x);
dfs(1,0),prework();
tot=0;for(RI i=1;i<=n;++i) h[i]=0;
while(Q--) {
qcnt=read();
for(RI i=1;i<=qcnt;++i) q[i]=read(),is[q[i]]=1;
work(),printf("%dn",ans);
}
return 0;
}
最后
以上就是正直篮球为你收集整理的loj 6184 无心行挽 虚树+DP+倍增题目分析代码的全部内容,希望文章能够帮你解决loj 6184 无心行挽 虚树+DP+倍增题目分析代码所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复