bzoj 3611: [Heoi2014]大工程
Description
国家有一个大工程,要给一个非常大的交通网络里建一些新的通道。
我们这个国家位置非常特殊,可以看成是一个单位边权的树,城市位于顶点上。
在 2 个国家 a,b 之间建一条新通道需要的代价为树上 a,b 的最短路径。
现在国家有很多个计划,每个计划都是这样,我们选中了 k 个点,然后在它们两两之间 新建 C(k,2)条 新通道。
现在对于每个计划,我们想知道:
1.这些新通道的代价和
2.这些新通道中代价最小的是多少
3.这些新通道中代价最大的是多少
Input
第一行 n 表示点数。
接下来 n-1 行,每行两个数 a,b 表示 a 和 b 之间有一条边。
点从 1 开始标号。 接下来一行 q 表示计划数。
对每个计划有 2 行,第一行 k 表示这个计划选中了几个点。
第二行用空格隔开的 k 个互不相同的数表示选了哪 k 个点。
Output
输出 q 行,每行三个数分别表示代价和,最小代价,最大代价。
Sample Input
10
2 1
3 2
4 1
5 2
6 4
7 5
8 6
9 7
10 9
5
2
5 4
2
10 4
2
5 2
2
6 1
2
6 1
2 1
3 2
4 1
5 2
6 4
7 5
8 6
9 7
10 9
5
2
5 4
2
10 4
2
5 2
2
6 1
2
6 1
Sample Output
3 3 3
6 6 6
1 1 1
2 2 2
2 2 2
6 6 6
1 1 1
2 2 2
2 2 2
HINT
n<=1000000
q<=50000并且保证所有k之和<=2*n
Source
题意:询问树上关键点两两间距离的和以及两两间距离的最大值和最小值;
首先讲一下虚树的构建过程,虚树是只包含关键点和关键点的Lca的一颗树。
具体构建方法是按照dfs序从小往大插入,然后用栈维护右链,栈顶元素表示当前栈中最后一个插入到虚树上的点。。。
我们考虑当前点a[i],栈顶元素zhan[tp]以及前两者的Lca(记为p)的关系。。
1.p=zhan[tp],那么a[i]在zhan[tp]的子树内,而且他们的路径间的点不会成为其余关键点的Lca。。。
那么把他们相连,弹栈,再把a[i]压入栈中,也就是插入这一条右链。。。
2.如果不是上一种情况,那么zhan[tp]和a[i]分居与p的两棵子树中,而且zhan[tp]内的关系已经处理完了,a[i]成为了新右链的开端,我们需要处理zhan[tp]到p的这一段。。。
那么zhan[tp]到Lca这一段上的点也显然不可能成为其余关键点间的Lca,那么可可以一直弹栈,并把zhan[tp]和zhan[tp-1]相连。。。一直弹到deep[zhan[tp-1]]<=deep[p];
1.如果deep[zhan[tp-1]]==deep[p],那么p和zhan[tp-1]是同一个点,而且弹到这一步的时候,p的含最开始的那个zhan[tp]的子树已经全部处理完毕了,break;
2.如果deep[zhan[tp-1]<deep[p],那么p到zhan[tp-1]这一段是有可能成为其余关键点的Lca的,那么这一段不能直接相连,
那么我们把p压入栈中,表示先要处理完p的子树内才能处理zhan[tp-1],但是原来的那棵子树还是处理完了,break。。。
最后我们再把尚在栈中的右链一直弹栈,最后保留的栈顶元素就是虚树的根。。。
然后讲一下dp,第一问的话就是单独考虑每条边会被经过多少次,跟HAOI2015很像。。。
然后求树的直径的话,有一种基于点分思想的dp,dp[i]表示i到子树中的最长路,然后每新加入一棵子树就把两条路径组合一下,并更新dp[i]。。。
记得特判关键点的路径也可以不组合。。。最小值类似。。。
// MADE BY QT666 #include<cstdio> #include<algorithm> #include<cmath> #include<iostream> #include<cstring> #define int long long using namespace std; typedef long long ll; const int N=2000050; const int Inf=19260817; struct data{ int head[N],to[N],nxt[N],v[N],cnt; void lnk(int x,int y){ if(x==y) return; to[++cnt]=y,nxt[cnt]=head[x],head[x]=cnt; to[++cnt]=x,nxt[cnt]=head[y],head[y]=cnt; } }g,g2; int size[N],dfn[N],tt,son[N],top[N],fa[N],deep[N],n,q; int a[N],zhan[N],k,sz[N],dp[N],f1[N],f2[N],bj[N],ans1,ans2; bool cmp(int a,int b){return dfn[a]<dfn[b];} void dfs1(int x,int f){ size[x]=1; for(int i=g.head[x];i;i=g.nxt[i]){ int y=g.to[i];if(y==f) continue; deep[y]=deep[x]+1;fa[y]=x;dfs1(y,x); size[x]+=size[y];if(size[y]>size[son[x]]) son[x]=y; } } void dfs2(int x,int f){ top[x]=f;dfn[x]=++tt; if(son[x]) dfs2(son[x],f); for(int i=g.head[x];i;i=g.nxt[i]){ int y=g.to[i];if(y==fa[x]||y==son[x]) continue; dfs2(y,y); } } int Lca(int x,int y){ while(top[x]!=top[y]){ if(deep[top[x]]<deep[top[y]]) swap(x,y); x=fa[top[x]]; } if(deep[x]<deep[y]) swap(x,y); return y; } void dfs(int x,int f){ sz[x]=bj[x];f1[x]=Inf,f2[x]=0;dp[x]=0; for(int i=g2.head[x];i;i=g2.nxt[i]){ int y=g2.to[i],v=deep[y]-deep[x];if(y==f) continue; dfs(y,x);dp[x]+=dp[y]+v*(sz[y])*(k-sz[y]); ans1=min(ans1,f1[x]+f1[y]+v);f1[x]=min(f1[x],f1[y]+v); ans2=max(ans2,f2[x]+f2[y]+v);f2[x]=max(f2[x],f2[y]+v); sz[x]+=sz[y]; } if(bj[x]){ans1=min(ans1,f1[x]),ans2=max(ans2,f2[x]),f1[x]=0;} g2.head[x]=0;bj[x]=0; } void solve(){ ans1=Inf,ans2=0; scanf("%lld",&k);for(int i=1;i<=k;i++) scanf("%lld",&a[i]),bj[a[i]]=1; sort(a+1,a+1+k,cmp);int tp=0;g2.cnt=0; for(int i=1;i<=k;i++){ if(!tp) {zhan[++tp]=a[i];continue;} int p=Lca(zhan[tp],a[i]); while(1){ if(deep[zhan[tp-1]]<=deep[p]){ g2.lnk(zhan[tp],p);tp--; if(zhan[tp]!=p) zhan[++tp]=p; break; } g2.lnk(zhan[tp],zhan[tp-1]);tp--; } zhan[++tp]=a[i]; } while(tp>1) g2.lnk(zhan[tp],zhan[tp-1]),tp--; dfs(zhan[tp],zhan[tp]); printf("%lld %lld %lld\n",dp[zhan[tp]],ans1,ans2); } main(){ scanf("%lld",&n); for(int i=1;i<n;i++){int x,y;scanf("%lld%lld",&x,&y);g.lnk(x,y);} dfs1(1,0);dfs2(1,1); scanf("%lld",&q);while(q--) solve(); return 0; }