bzoj3611 [Heoi2014]大工程 虚树+树形dp

40 篇文章 0 订阅
3 篇文章 0 订阅

Description


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

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

Solution


考虑树形dp,f[x]表示x子树内关键点到x的距离和,g[x]表示x子树内关键点数量,转移比较显然
然后二三问就是mx[x]和mn[x]表示x与最近/最远关键点的距离
我们发现Σk不大于是上虚树就over了

最开始有一个nlog^2n的做法,第一个答案就暴力插入关键点然后树链剖分求,第二、三个答案就是枚举lca然后子树内求minmax。不过仔细撕烤一下就可以发现这个和上面的dp其实是等价的。。

Code


#include <stdio.h>
#include <string.h>
#include <algorithm>
#define rep(i,st,ed) for (int i=st;i<=ed;++i)
#define drp(i,st,ed) for (int i=st;i>=ed;--i)

typedef long long LL;
const LL INF=1e15;
const int N=2000005;

struct Graph {
	struct edge {int y,w,next;} e[N*2];
	int ls[N],fa[N],edCnt;

	edge operator [](int x) {
		return e[x];
	}

	void add_edge(int x,int y,int w) {
		e[++edCnt]=(edge) {y,w,ls[x]}; ls[x]=edCnt;
	}
} G,T;

int bl[N],dep[N],pos[N],size[N];

int read() {
	int x=0,v=1; char ch=getchar();
	for (;ch<'0'||ch>'9';v=(ch=='-')?(-1):(v),ch=getchar());
	for (;ch<='9'&&ch>='0';x=x*10+ch-'0',ch=getchar());
	return x*v;
}

void dfs1(int now) {
	size[now]=1;
	for (int i=G.ls[now];i;i=G[i].next) {
		if (G[i].y==G.fa[now]) continue;
		G.fa[G[i].y]=now; dep[G[i].y]=dep[now]+1;
		dfs1(G[i].y); size[now]+=size[G[i].y];
	}
}

void dfs2(int now,int up) {
	bl[now]=up; pos[now]=++pos[0];
	int mx=0;
	for (int i=G.ls[now];i;i=G[i].next) {
		if (G[i].y!=G.fa[now]&&size[G[i].y]>size[mx]) mx=G[i].y;
	}
	if (!mx) return ;
	dfs2(mx,up);
	for (int i=G.ls[now];i;i=G[i].next) {
		if (G[i].y!=G.fa[now]&&G[i].y!=mx) dfs2(G[i].y,G[i].y);
	}
}

int get_lca(int x,int y) {
	for (;bl[x]!=bl[y];) {
		if (dep[bl[x]]<dep[bl[y]]) std:: swap(x,y);
		x=G.fa[bl[x]];
	}
	return dep[x]<dep[y]?x:y;
}

int get_dis(int x,int y) {
	int lca=get_lca(x,y);
	return dep[x]+dep[y]-dep[lca]*2;
}

bool cmp(int x,int y) {
	return pos[x]<pos[y];
}

void solve() {
	static int p[N],h[N],stack[N];
	static LL mx[N],mn[N],f[N],g[N];
	int k=read(),top=0,m=k;
	rep(i,1,k) h[i]=p[i]=read();
	std:: sort(p+1,p+k+1,cmp);
	rep(i,1,k) {
		if (!top) {
			stack[++top]=p[i];
			continue;
		}
		int lca=get_lca(p[i],stack[top]);
		for (;dep[lca]<dep[stack[top]];top--) {
			if (top&&dep[stack[top-1]]<=dep[lca]) {
				T.fa[stack[top]]=lca;
			}
		}
		if (lca!=stack[top]) {
			T.fa[lca]=stack[top];
			stack[++top]=lca;
			h[++m]=lca;
			f[lca]=g[lca]=0;
			mn[lca]=INF; mx[lca]=0;
		}
		T.fa[p[i]]=stack[top];
		stack[++top]=p[i];
	}
	rep(i,1,k) {
		f[p[i]]=0,g[p[i]]=1;
		mn[p[i]]=mx[p[i]]=0;
	}
	LL ans1=0,ans2=INF,ans3=0;
	std:: sort(h+1,h+m+1,cmp);
	rep(i,1,m) T.add_edge(T.fa[h[i]],h[i],get_dis(T.fa[h[i]],h[i]));
	drp(ti,m,1) {
		int now=h[ti];
		for (int i=T.ls[now];i;i=T[i].next) {
			ans1+=(f[T[i].y]+g[T[i].y]*T[i].w)*g[now]+f[now]*g[T[i].y];
			ans2=std:: min(ans2,mn[now]+mn[T[i].y]+T[i].w);
			ans3=std:: max(ans3,mx[now]+mx[T[i].y]+T[i].w);
			g[now]+=g[T[i].y];
			f[now]+=f[T[i].y]+g[T[i].y]*T[i].w;
			mx[now]=std:: max(mx[now],mx[T[i].y]+T[i].w);
			mn[now]=std:: min(mn[now],mn[T[i].y]+T[i].w);
		}
	}
	printf("%lld %lld %lld\n", ans1,ans2,ans3);
	T.edCnt=0;
	rep(i,1,m) T.ls[h[i]]=T.fa[h[i]]=0;
}

int main(void) {
	freopen("data.in","r",stdin);
	freopen("myp.out","w",stdout);
	int n=read();
	rep(i,2,n) {
		int x=read(),y=read();
		G.add_edge(x,y,1);
		G.add_edge(y,x,1);
	}
	dfs1(dep[1]=1); dfs2(1,1);
	for (int Q=read();Q--;) solve();
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值