[HEOI2014][bzoj3611] 大工程 [虚树+dp]
题面:
思路:
又是一道虚树入门级的题目,但是这道题的实际难点在于dp
首先,这道题是可以点分治做的,而且因为6s时限随便浪,所以写点分治也不是不可以
但是,dp因为$O\left(n\right)$的高效率,依然最好优先考虑
对于这道题的dp,我们首先记录几个数组:
siz[u]表示以u为根(包括u)的子树中,询问点的总个数
minn[u]表示子树中最短的一条由子树根到询问点的路径,maxn[u]则是最长的
sum[u]则代表这棵子树中所有要求路径的长度总和
最小路径和最大路径的dp都比较简单了
每次进入子树dp结束以后,用minn[v]更新minn[u],然后用minn[u]+minn[v]+dis(u,v)更新答案
maxn同理
注意这里虽然是单位边权,但是建完虚树以后边的长度便不再固定是1了
sum的dp比较麻烦一点
每次子树v的dp结束以后,先把siz[u]+=siz[v],然后sum[u]+=sum[v]+siz[v]*(m-siz[v])*dis(u,v)
这里的m是本次询问的所有点数
这里相当于是把v子树中的所有路径(内部的路径)的值,加上经过(u,v)这条边的路径总数乘以dis(u,v),然后加入到了总答案中
这样每个sum都会包含每条边应该经过的次数,答案是正确的
虚树建立起来以后,直接dp就可以了。注意这道题里面不能固定以1作为起点,因为路径很可能不经过1,所以需要另一种建立虚树的方法,详见代码
Code:
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #define ll long long 6 #define inf 1e18 7 using namespace std; 8 inline ll read(){ 9 ll re=0,flag=1;char ch=getchar(); 10 while(ch>'9'||ch<'0'){ 11 if(ch=='-') flag=-1; 12 ch=getchar(); 13 } 14 while(ch>='0'&&ch<='9') re=(re<<1)+(re<<3)+ch-'0',ch=getchar(); 15 return re*flag; 16 } 17 ll n,m,dep[1000010],st[1000010][22],dfn[1000010],clk; 18 ll sum[1000010],minn[1000010],maxn[1000010],siz[1000010]; 19 struct graph{ 20 ll first[1000010],cnt; 21 struct edge{ 22 ll to,next; 23 }a[2000010]; 24 graph(){ 25 memset(first,-1,sizeof(first));cnt=0; 26 } 27 void init(){cnt=0;} 28 inline void add(ll u,ll v){ 29 if(u==v) return; 30 a[++cnt]=(edge){v,first[u]};first[u]=cnt; 31 } 32 }G,g; 33 void dfs(ll u,ll f){ 34 ll i,v; 35 dep[u]=dep[f]+1;dfn[u]=++clk;st[u][0]=f; 36 for(i=G.first[u];~i;i=G.a[i].next){ 37 v=G.a[i].to; 38 if(v==f) continue; 39 dfs(v,u); 40 } 41 } 42 void ST(){ 43 ll i,j; 44 for(j=1;j<=21;j++) for(i=1;i<=n;i++) st[i][j]=st[st[i][j-1]][j-1]; 45 } 46 ll lca(ll l,ll r){ 47 if(dep[l]>dep[r]) swap(l,r); 48 ll i; 49 for(i=21;i>=0;i--) if(dep[st[r][i]]>=dep[l]) r=st[r][i]; 50 if(l==r) return l; 51 for(i=21;i>=0;i--) 52 if(st[l][i]!=st[r][i]){ 53 l=st[l][i]; 54 r=st[r][i]; 55 } 56 return st[l][0]; 57 } 58 ll q[1000010],s[1000010],top,ans1,ans2,ans3; 59 bool vis[1000010]; 60 bool cmp(ll l,ll r){ 61 return dfn[l]<dfn[r]; 62 } 63 void dp(ll u){ 64 ll i,v,dis; 65 sum[u]=0;minn[u]=inf;maxn[u]=0;siz[u]=(vis[u]); 66 for(i=g.first[u];~i;i=g.a[i].next){ 67 v=g.a[i].to;g.first[u]=g.a[i].next; 68 dp(v);dis=dep[v]-dep[u]; 69 ans2=min(ans2,minn[u]+minn[v]+dis);minn[u]=min(minn[u],minn[v]+dis); 70 ans3=max(ans3,maxn[u]+maxn[v]+dis);maxn[u]=max(maxn[u],maxn[v]+dis); 71 siz[u]+=siz[v]; 72 sum[u]+=sum[v]+siz[v]*(m-siz[v])*dis; 73 } 74 if(vis[u]) vis[u]=0,ans2=min(ans2,minn[u]),ans3=max(ans3,maxn[u]),minn[u]=0; 75 } 76 void solve(){ 77 m=read();ll i,j,grand;g.init(); 78 for(i=1;i<=m;i++) q[i]=read(),vis[q[i]]=1; 79 sort(q+1,q+m+1,cmp);top=0; 80 for(i=1;i<=m;i++){ 81 if(!top){s[++top]=q[i];continue;}//因为现在没有一个1号结点压在栈底不可能弹出,所以需要检测栈是否为空 82 grand=lca(q[i],s[top]); 83 while(1){ 84 if(dep[s[top-1]]<=dep[grand]){ 85 g.add(grand,s[top--]); 86 if(s[top]!=grand) s[++top]=grand; 87 break; 88 } 89 g.add(s[top-1],s[top]);top--; 90 } 91 if(s[top]!=q[i]) s[++top]=q[i]; 92 } 93 while(--top>=1) g.add(s[top],s[top+1]); 94 ans1=ans3=0;ans2=inf; 95 dp(s[1]);//这里从栈中最后剩下的元素开始dp,因为它就是整棵虚树的根了 96 printf("%lld %lld %lld\n",sum[s[1]],ans2,ans3); 97 return; 98 } 99 int main(){ 100 ll i,j,t1,t2;n=read(); 101 for(i=1;i<n;i++){ 102 t1=read(),t2=read(); 103 G.add(t1,t2);G.add(t2,t1); 104 } 105 dfs(1,0);ST(); 106 ll q=read(); 107 for(i=1;i<=q;i++) solve(); 108 }