bzoj3611: [Heoi2014]大工程
传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=3611
思路:构建虚树DP
首先这种题有一个特征,就是所有询问的总点数是O(n)的
那么就可以考虑对每次询问建一棵虚树,再在虚树上DP。
那么我们对于每次询问,就不一定要把整棵树建出来,而是只要管一部分点即可
比如u,v两点之间没有其他询问点,那么我们就可以把uv直接连起来,中间的点是什么我们并不关心。
我们只要建出这样一棵树即可:只含当前询问点和它们的lca,并且相对位置关系不变
举个例子:原树是这样的,假如选了1、5、8、10号节点
构建出来的虚树就是:
怎样构建虚树?
先对原图做一边dfs预处理,得出dfn和一些乱七八糟的东西。
对于每次询问,先把所有询问点按dfn排序,一个一个插入。
我们要用栈维护一个叫“最右链”的东西,就是最右边的一条链...
每次和最右链的最深的一个点求lca,并动态维护之。
如果栈为空,直接加进栈,
否则求出栈顶元素stk[top]和当前点的lca
如果dfn[lca]<dfn[stk[top]],那就一直弹栈直到dfn[lca]>=dfn[stk[top]],因为这些点都不会在新的最右链里。
(记得特判lca是否已在栈中)
看图直观理解就是左边的点与当前点求lca不会贡献出新的点,所以只用管最右链即可
这样我们每次就可以O(m)的构建虚树了,这也是为什么这些题都有Σq[i]<=O(n)的限制。
用虚树还要满足一个条件,就是要维护的信息
例如,和,最大,最小,都有类似于前缀和的性质,
就是我们可以从v(u的后代)直接求出u的答案,而不需要遍历u到v的所有边
否则虚树就没有降低复杂度,因为每次还是要在原树上走。
对于这题,剩下的只有在虚树上treeDP了。
记录f[i]表示在i的子树中的路径总长,siz[i]表示i的子树中询问点的个数
maxs[i]表示i子树中询问点到根的最长路长,mins[i]同理。
转移很显然 f[i]=f[son[i]]+siz[son[y]]*(cnt-siz[son[y]])*dis(i,son[i]) (cnt是此次询问总点数)
对于maxs[i]
如果i是询问点,那就只要在子树中找另一个询问点即可
即我们可以直接用maxs[i]更新答案。
否则我们要找两个点,用maxs[i]+maxs[son[i]]+dis(i,son[i])更新答案
最小同理
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> typedef long long ll; const int maxn=1000010,maxm=maxn<<1,maxk=23,inf=1e9; using namespace std; int n,q,dis[maxn],dfn[maxn],tim,dep[maxn],fa[maxn][maxk],num,poi[maxn]; int stk[maxn],top,siz[maxn],maxs[maxn],mins[maxn],ans1,ans2; ll f[maxn];bool bo[maxn]; bool cmp(int a,int b){return dfn[a]<dfn[b];} struct Tgraph{ int pre[maxm],now[maxn],son[maxm],tot; void add(int a,int b){pre[++tot]=now[a],now[a]=tot,son[tot]=b;} void dfs1(int x){ dfn[x]=++tim; for (int i=1;i<=20;i++) fa[x][i]=fa[fa[x][i-1]][i-1]; for (int y=now[x];y;y=pre[y]) if (son[y]!=fa[x][0]) dis[son[y]]=dis[x]+1,dep[son[y]]=dep[x]+1,fa[son[y]][0]=x,dfs1(son[y]); } void dfs2(int x){ siz[x]=bo[x],maxs[x]=0,mins[x]=inf,f[x]=0; for (int y=now[x];y;y=pre[y]){ int d=dis[son[y]]-dis[x]; dfs2(son[y]),siz[x]+=siz[son[y]]; ans1=min(ans1,mins[x]+mins[son[y]]+d),mins[x]=min(mins[x],mins[son[y]]+d); ans2=max(ans2,maxs[x]+maxs[son[y]]+d),maxs[x]=max(maxs[x],maxs[son[y]]+d); f[x]+=f[son[y]]+1ll*siz[son[y]]*(num-siz[son[y]])*d; } if (bo[x]) ans1=min(ans1,mins[x]),ans2=max(ans2,maxs[x]),mins[x]=0; now[x]=0; } }g1,g2; int lca(int a,int b){ if (dep[a]<dep[b]) swap(a,b); for (int h=dep[a]-dep[b],i=20;i>=0;i--) if (h>=(1<<i)) h-=(1<<i),a=fa[a][i]; if (a==b) return a; for (int i=20;i>=0;i--) if (fa[a][i]!=fa[b][i]) a=fa[a][i],b=fa[b][i]; return fa[a][0]; } void work(){ top=0; for (int i=1;i<=num;i++){ if (!top){stk[++top]=poi[i];continue;} int u=lca(stk[top],poi[i]); while (dfn[u]<dfn[stk[top]]){ if (dfn[u]>=dfn[stk[top-1]]){ g2.add(u,stk[top]); if (stk[--top]!=u) stk[++top]=u; break; } g2.add(stk[top-1],stk[top]),top--; } stk[++top]=poi[i]; } while (top>1) g2.add(stk[top-1],stk[top]),top--; ans1=inf,ans2=0,g2.dfs2(stk[1]); printf("%lld %d %d\n",f[stk[1]],ans1,ans2); for (int i=1;i<=num;i++) bo[poi[i]]=0;g2.tot=0; } int main(){ scanf("%d",&n); for (int i=1,a,b;i<n;i++) scanf("%d%d",&a,&b),g1.add(a,b),g1.add(b,a); g1.dfs1(1),scanf("%d",&q); for (int i=1;i<=q;i++){ scanf("%d",&num); for (int j=1;j<=num;j++) scanf("%d",&poi[j]),bo[poi[j]]=1; sort(poi+1,poi+1+num,cmp),work(); } return 0; }