P4103 [HEOI2014]大工程
题目
分析
虚树\(+dp\)。
很明显其实题目就是建出虚树,然后对于三问分别考虑:
第一问就是可以考虑计算每一条边的贡献,也就是左端的 \(siz\) 和右端的 \(siz\) 的乘积,就是这条边经过次数。
二三问就是经典的树的直径的 \(dp\) 做法。
代码
#include<bits/stdc++.h>
using namespace std;
template <typename T>
inline void read(T &x){
x=0;bool f=false;char ch=getchar();
while(!isdigit(ch)){f|=ch=='-';ch=getchar();}
while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
x=f?-x:x;
return ;
}
template <typename T>
inline void write(T x){
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10^48);
return ;
}
#define ll long long
const int N=2e6+5,t=20,INF=1e9+7;
int n,m,Ans2,Ans3;
ll Ans1;
int head[N],to[N],nex[N],val[N],pre[N],idx;
int dfn[N],q[N],sta[N],k,top,DFN;
bool vis[N],flag;
int Top[N],dep[N],siz[N],fa[N],son[N];
inline void add(int u,int v){
nex[++idx]=head[u];
to[idx]=v;
pre[idx]=u;
head[u]=idx;
val[idx]=dep[v]-dep[u];
return ;
}
void dfs1(int x,int f){
siz[x]=1;dep[x]=dep[f]+1;fa[x]=f;
for(int i=head[x];i;i=nex[i]){
int y=to[i];
if(y==f) continue;
dfs1(y,x);siz[x]+=siz[y];
if(siz[son[x]]<siz[y]) son[x]=y;
}
return ;
}
void dfs2(int x,int f){
if(son[fa[x]]==x) Top[x]=Top[f];
else Top[x]=x;
dfn[x]=++DFN;
if(son[x]) dfs2(son[x],x);
for(int i=head[x];i;i=nex[i]){
int y=to[i];
if(y==f||y==son[x]) continue;
dfs2(y,x);
}
return ;
}
int QueryLca(int x,int y){
while(Top[x]!=Top[y]){
if(dep[Top[x]]<dep[Top[y]]) swap(x,y);
x=fa[Top[x]];
}
return dep[x]<dep[y]?x:y;
}
inline bool Cmp(const int &x,const int &y){return dfn[x]<dfn[y];}
int Max1[N],Max2[N],Min1[N],Min2[N];
void DP(int x,int f){
Max1[x]=Max2[x]=-INF,Min1[x]=Min2[x]=INF;siz[x]=0;
if(vis[x]) siz[x]++,Min1[x]=Max1[x]=0;
for(int i=head[x];i;i=nex[i]){
int y=to[i];
if(y==f) continue;
DP(y,x);siz[x]+=siz[y];
if(Max1[y]+val[i]>Max1[x]) Max2[x]=Max1[x],Max1[x]=Max1[y]+val[i];
else if(Max1[y]+val[i]>Max2[x]) Max2[x]=Max1[y]+val[i];
if(Min1[y]+val[i]<Min1[x]) Min2[x]=Min1[x],Min1[x]=Min1[y]+val[i];
else if(Min1[y]+val[i]<Min2[x]) Min2[x]=Min1[y]+val[i];
}
Ans2=min(Ans2,Min1[x]+Min2[x]);
Ans3=max(Ans3,Max1[x]+Max2[x]);
return ;
}
int clear[N],Cnt;
void BuildVTree(){
sort(q+1,q+k+1,Cmp);
top=0,sta[++top]=1;head[1]=0;idx=0;
for(int i=1;i<=k;i++){
const int x=q[i];
if(x==1) continue;head[x]=0;
if(top<2){sta[++top]=x;continue;}
const int lca=QueryLca(sta[top],x);
if(sta[top]==lca){sta[++top]=x;continue;}
while(top>1&&dfn[sta[top-1]]>=dfn[lca]) add(sta[top-1],sta[top]),top--;
if(sta[top]!=lca) head[lca]=0,add(lca,sta[top]),sta[top]=lca;
sta[++top]=x;
}
while(top>1) add(sta[top-1],sta[top]),top--;
return ;
}
signed main(){
read(n);
for(int i=1;i<n;i++){
int u,v,w;
read(u),read(v);
add(u,v),add(v,u);
}
read(m);
dfs1(1,0);dfs2(1,0);
while(m--){
read(k);flag=false;Ans2=INF,Ans3=-INF;Ans1=0;
for(int i=1;i<=k;i++) read(q[i]),vis[q[i]]=true;
BuildVTree();
DP(1,0);
for(int i=1;i<=idx;i++){
int x=pre[i],y=to[i];
if(dep[x]<dep[y]) swap(x,y);
Ans1+=1ll*siz[x]*(k-siz[x])*val[i];
}
write(Ans1),putchar(' '),write(Ans2),putchar(' '),write(Ans3),putchar('\n');
for(int i=1;i<=k;i++) vis[q[i]]=false;
}
return 0;
}
感受
计算第一问的方式是一个很好的思路,可以用于路径计算全体贡献的时候,一条一条路径不好计算,那么我们可以考虑算出每一条边的贡献,同样也能达到目的。