P4103 [HEOI2014]大工程

题目

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;
} 

感受

计算第一问的方式是一个很好的思路,可以用于路径计算全体贡献的时候,一条一条路径不好计算,那么我们可以考虑算出每一条边的贡献,同样也能达到目的。

posted @ 2021-05-07 00:05  __Anchor  阅读(42)  评论(0编辑  收藏  举报