[HEOI2014][bzoj3611] 大工程 [虚树+dp]

题面:

传送门

思路:

又是一道虚树入门级的题目,但是这道题的实际难点在于dp

首先,这道题是可以点分治做的,而且因为6s时限随便浪,所以写点分治也不是不可以

但是,dp因为$Oleft(n ight)$的高效率,依然最好优先考虑

对于这道题的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
",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 }
原文地址:https://www.cnblogs.com/dedicatus545/p/8570043.html