bzoj3611 [Heoi2014]大工程

Description

国家有一个大工程,要给一个非常大的交通网络里建一些新的通道。 
我们这个国家位置非常特殊,可以看成是一个单位边权的树,城市位于顶点上。 
在 2 个国家 a,b 之间建一条新通道需要的代价为树上 a,b 的最短路径。
 现在国家有很多个计划,每个计划都是这样,我们选中了 k 个点,然后在它们两两之间 新建 C(k,2)条 新通道。
现在对于每个计划,我们想知道:
 1.这些新通道的代价和
 2.这些新通道中代价最小的是多少 
3.这些新通道中代价最大的是多少

Input

第一行 n 表示点数。

 接下来 n-1 行,每行两个数 a,b 表示 a 和 b 之间有一条边。
点从 1 开始标号。 接下来一行 q 表示计划数。
对每个计划有 2 行,第一行 k 表示这个计划选中了几个点。
 第二行用空格隔开的 k 个互不相同的数表示选了哪 k 个点。

Output

输出 q 行,每行三个数分别表示代价和,最小代价,最大代价。 

Sample Input

10
2 1
3 2
4 1
5 2
6 4
7 5
8 6
9 7
10 9
5
2
5 4
2
10 4
2
5 2
2
6 1
2
6 1

Sample Output

3 3 3
6 6 6
1 1 1
2 2 2
2 2 2

HINT

n<=1000000 


q<=50000并且保证所有k之和<=2*n 


正解:虚树+树形dp。

对于每次询问,建立一棵虚树。在虚树上跑树形dp。这题有点麻烦,主要是dp的姿势吧。。求总数就是记录当前点size,然后加加减减就行了,求最小值就看当前点是否是关键点,如果是关键点就直接取它的子树路径最小值,否则就是它的两个儿子的子树路径最小值相加。求最大值同理。


//It is made by wfj_2048~
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <vector>
#include <cmath>
#include <queue>
#include <stack>
#include <map>
#include <set>
#define inf (1<<30)
#define N (1000010)
#define il inline
#define RG register
#define ll long long
#define File(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)

using namespace std;

struct edge{ int nt,to,dis; }g[2*N];

int head[N],size[N],top[N],fa[N],son[N],dis[N],dep[N],dfn[N],ed[N],a[N],st[N],vi[N],sz[N],dp1[N],dp2[N],n,m,k,u,v,q,num,cnt,ans1,ans2;
ll dp[N];

il int gi(){
    RG int x=0,q=1; RG char ch=getchar(); while ((ch<'0' || ch>'9') && ch!='-') ch=getchar();
    if (ch=='-') q=-1,ch=getchar(); while (ch>='0' && ch<='9') x=x*10+ch-48,ch=getchar(); return q*x;
}

il void insert(RG int from,RG int to){ g[++num]=(edge){head[from],to},head[from]=num; return; }

il int cmp(const int &a,const int &b){ return dfn[a]<dfn[b]; }

il void dfs(RG int x){
    dp[x]=0,dp1[x]=inf,dp2[x]=0,sz[x]=vi[x];
    for (RG int i=head[x];i;i=g[i].nt){
	RG int v=g[i].to,d=dis[v]-dis[x]; dfs(v);
	sz[x]+=sz[v]; dp[x]+=dp[v]+(ll)sz[v]*(ll)(k-sz[v])*(ll)d;
	ans1=min(ans1,dp1[x]+dp1[v]+d),dp1[x]=min(dp1[x],dp1[v]+d);
	ans2=max(ans2,dp2[x]+dp2[v]+d),dp2[x]=max(dp2[x],dp2[v]+d);
    }
    if (vi[x]) ans1=min(ans1,dp1[x]),ans2=max(ans2,dp2[x]),dp1[x]=0;
    head[x]=0; return;
}

il void dfs1(RG int x,RG int p){
    dfn[x]=++cnt,dep[x]=dep[p]+1,fa[x]=p,size[x]=1;
    for (RG int i=head[x];i;i=g[i].nt){
	RG int v=g[i].to; if (v==p) continue;
	dis[v]=dis[x]+1; dfs1(g[i].to,x); size[x]+=size[v];
	if (size[son[x]]<=size[v]) son[x]=v;
    }
    ed[x]=cnt; return;
}

il void dfs2(RG int x,RG int p,RG int a){
    top[x]=a; if (son[x]) dfs2(son[x],x,a);
    for (RG int i=head[x];i;i=g[i].nt){
	RG int v=g[i].to;
	if (v==p || v==son[x]) continue;
	dfs2(v,x,v);
    }
    head[x]=0; return;
}

il int lca(RG int u,RG int v){
    while (top[u]!=top[v]){
	if (dep[top[u]]<dep[top[v]]) swap(u,v);
	u=fa[top[u]];
    }
    return dep[u]<dep[v] ? u : v;
}

il void work(){
    n=gi(); for (RG int i=1;i<n;++i) u=gi(),v=gi(),insert(u,v),insert(v,u); dfs1(1,0),dfs2(1,0,1); q=gi();
    for (RG int i=1;i<=q;++i){
	k=gi(),m=k; for (RG int i=1;i<=k;++i) vi[a[i]=gi()]=1; sort(a+1,a+k+1,cmp); RG int Lca=a[1];
	for (RG int i=2;i<=k;++i){ if (ed[a[i-1]]<dfn[a[i]]) a[++m]=lca(a[i-1],a[i]); Lca=lca(Lca,a[i]); }
	a[++m]=Lca; sort(a+1,a+m+1,cmp); m=unique(a+1,a+m+1)-a-1; RG int top=1; st[top]=a[1],num=0;
	for (RG int i=2;i<=m;++i){ while (top && ed[st[top]]<dfn[a[i]]) top--; insert(st[top],a[i]),st[++top]=a[i]; }
	ans1=inf,ans2=0,dfs(Lca); printf("%lld %d %d\n",dp[Lca],ans1,ans2); for (RG int i=1;i<=m;++i) vi[a[i]]=0;
    }
    return;
}

int main(){
    File("project");
    work();
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值