• [HEOI 2014] 大工程


    前几天讲了虚树,今天就来切一道虚树的题喽。

    题目概述:

    给你一棵树,有(q)个询问,每个询问给出(k)个点,两两连边,边的长度为其在树上的距离,求出这(k)个点连边总长度、最短的一条边以及最长的一条边。

    其中(sum_{i=1}^qk<=5^4)

    大体思路:

    显然是道虚树的题,那就先构建棵虚树。

    对于第二三两个询问,我们都可以通过一个非常简单的DP来实现,每个询问分别用两个数组求出以它为根,第一、第二(大/小)的路径的值,但是要注意这里的两个值不能都是从同一个子树中转移过来的。

    关于第一个询问,我们考虑每一条边对于答案的贡献,不难发现其被加的次数为(siz[u] imes siz[v]),那么我们把每一条边的贡献加起来即可。

    具体实现 (坑爹细节)

    对于非询问点(即通过(LCA)关系而加入虚树的点),我们不妨称之为关系户。

    关系户不能进入(siz)的统计(显然)。

    关系户只能作为连接点,即只能以第一、第二(大/小)的路径的值相加来更新答案。(显然)

    虚树的根为所有点加入完毕后,剩下的栈中的那个栈顶。(显然)

    代码:

    #include <bits/stdc++.h>
    using namespace std;
    const int N=1e6+5,inf=1e9;
    int f[N][21],fir[N],sec[N],Fir[N],Sec[N],nxt[N<<1],vet[N<<1],head[N],dep[N],dfn[N],siz[N],stk[N],a[N];
    int n,m,ans1,ans2,Q,u,v,tot,tim,top,rt;
    long long ans0;
    bool flag[N];
    struct Edge{
    	int v,d;
    };
    vector <Edge> E[N];
    void add(int u,int v){
    	nxt[++tot]=head[u];
    	vet[tot]=v;
    	head[u]=tot;
    }
    void dfs0(int u,int fa){
    	f[u][0]=fa,dep[u]=dep[fa]+1,dfn[u]=++tim;
    	for (int i=1;i<=20;i++)
    		f[u][i]=f[f[u][i-1]][i-1];
    	for (int i=head[u];i;i=nxt[i]){
    		int v=vet[i];
    		if (v==fa) continue;
    		dfs0(v,u);
    	}
    }
    int lca(int a,int b){
    	if (dep[a]<dep[b]) swap(a,b);
    	if (b==0) return 0;
    	int res=inf;
    	for (int i=20;i>=0;i--)
    		if (dep[f[a][i]]>=dep[b]) a=f[a][i];
    	if (a==b) return a;
    	for (int i=20;i>=0;i--)
    		if (f[a][i]!=f[b][i]) 
    			a=f[a][i],b=f[b][i];
    	return f[a][0];
    }
    int dis(int a,int b){return dep[a]+dep[b]-dep[lca(a,b)]*2;}
    void ins(int x){
    	int l=lca(stk[top],x);
    	while (top>1&&dep[stk[top-1]]>dep[l]){
    		E[stk[top]].push_back((Edge){stk[top-1],dis(stk[top],stk[top-1])});
    		E[stk[top-1]].push_back((Edge){stk[top],dis(stk[top],stk[top-1])});
    		top--;
    	}
    	if (dep[l]<dep[stk[top]]){
    		E[l].push_back((Edge){stk[top],dis(stk[top],l)});
    		E[stk[top]].push_back((Edge){l,dis(stk[top],l)});
    		top--;
    	}
    	if (stk[top]!=l) stk[++top]=l;
    	stk[++top]=x;
    }
    void dfs(int u,int fa){
    	Fir[u]=Sec[u]=inf,fir[u]=sec[u]=siz[u]=0;
    	if (flag[u]) siz[u]=1,Fir[u]=0;
    	for (int i=0;i<E[u].size();i++){
    		int v=E[u][i].v;
    		if (v==fa) continue;
    		dfs(v,u);
    		ans0+=1ll*E[u][i].d*siz[v]*(m-siz[v]);
    		siz[u]+=siz[v];
    		int x=Fir[v]+E[u][i].d;
    		if (x<=Fir[u])
    			Sec[u]=Fir[u],Fir[u]=x;
    		else
    			if (x<Sec[u]) Sec[u]=x;
    		x=fir[v]+E[u][i].d;
    		if (x>=fir[u])
    			sec[u]=fir[u],fir[u]=x;
    		else
    			if (x>sec[u]) sec[u]=x;
    	}
    	ans1=min(ans1,Fir[u]+Sec[u]);
    	ans2=max(ans2,fir[u]+sec[u]);
    	E[u].clear();
    }
    bool cmp(int a,int b){return dfn[a]<dfn[b];}
    int main(){
    	scanf("%d",&n);
    	for (int i=1;i<n;i++){
    		scanf("%d%d",&u,&v);
    		add(u,v),add(v,u);
    	}
    	dfs0(1,0);
    	scanf("%d",&Q);
    	while (Q--){
    		ans0=ans2=0,ans1=inf;
    		scanf("%d",&m);
    		if (m==1){
    			puts("0 0 0");
    			continue;
    		}
    		top=0;
    		for (int i=1;i<=m;i++)
    			scanf("%d",&a[i]),flag[a[i]]=1;
    		rt=a[1];
    		sort(a+1,a+1+m,cmp);
    		for (int i=1;i<=m;i++) ins(a[i]);
    		while (top>1){
    			E[stk[top]].push_back((Edge){stk[top-1],dis(stk[top],stk[top-1])});
    			E[stk[top-1]].push_back((Edge){stk[top],dis(stk[top],stk[top-1])});
    			top--;
    		}
    		dfs(stk[top],0);
    		for (int i=1;i<=m;i++) flag[a[i]]=0;
    		printf("%lld %d %d
    ",ans0,ans1,ans2);
    	}
    	return 0;
    }
    
  • 相关阅读:
    windows服务创建与管理
    html前端技术:??
    C#整数三种强制类型转换int、Convert.ToInt32()、int.Parse()的区别
    convert转化成特定日期格式
    关于android性能,内存优化(转载)
    不错的Android博客
    十步优化SQL Server中的数据访问(转载)
    数据库SQL优化大总结之 百万级数据库优化方案(转载)
    SQL Server数据库性能优化之SQL语句篇(转载)
    50种方法优化SQL Server数据库查询(转载)
  • 原文地址:https://www.cnblogs.com/WR-Eternity/p/11787278.html
Copyright © 2020-2023  润新知