[COCI2014-2015#1] Kamp

CXVI.[COCI2014-2015#1] Kamp

一看题面,突然感觉很弱智,不就是求出以每个点为根到其它所有特殊点的距离之和吗?这不是随随便便换个根就完事了吗?

然后兴冲冲敲出来,一测样例全挂。

后来发现并不是这样的,因为车上可以同时搭载多人,且车最后可以就停在某个地方不回去了。

稍微想想可以发现,最终停着的位置,一定是离起点最远的特殊点;故我们直接使用 multiset 维护一下就可以换根了。求出每个节点离其最远的特殊点的距离后,以它为根的答案就是\((\text{从它出发到达所有点再回到它的最短距离})-(\text{上述DP值})\)

然后,因为车上可以搭载多人,所以实际上上述最短距离就是二倍以当前点和所有特殊点构成的虚树大小。这个可以直接通过求虚树大小的做法(按照dfs序排序再求出两两相邻点间距离)或者干脆直接再来一发换根解决。

时间复杂度\(O(n\log n)\)

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m,head[500100],cnt,g[500100];
ll f[500100],h[500100];
bool sp[500100];
struct node{
	int to,next,val;
}edge[1001000];
void ae(int u,int v,int w){
	edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
	edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=w,head[v]=cnt++;
}
multiset<ll>s[500100];
void dfs1(int x,int fa){
	if(sp[x])g[x]++,h[x]=0,s[x].insert(0);
	for(int i=head[x],y;i!=-1;i=edge[i].next){
		if((y=edge[i].to)==fa)continue;
		dfs1(y,x),f[x]+=f[y]+1ll*!!g[y]*edge[i].val,g[x]+=g[y];
		h[x]=max(h[x],h[y]+edge[i].val),s[x].insert(h[y]+edge[i].val);
	}
}
void dfs2(int x,int fa){
	for(int i=head[x],y;i!=-1;i=edge[i].next){
		if((y=edge[i].to)==fa)continue;
		ll fx=f[x]-f[y]-1ll*!!g[y]*edge[i].val;
		int gx=g[x]-g[y];
		f[y]+=fx+1ll*!!gx*edge[i].val;
		g[y]+=gx;
		
		s[x].erase(s[x].find(h[y]+edge[i].val));
		if(!s[x].empty())s[y].insert(*s[x].rbegin()+edge[i].val);
		s[x].insert(h[y]+edge[i].val);
		h[y]=*s[y].rbegin();
		
		dfs2(y,x);
	}
}
int main(){
	scanf("%d%d",&n,&m),memset(head,-1,sizeof(head)),memset(h,0xc0,sizeof(h));
	for(int i=1,x,y,z;i<n;i++)scanf("%d%d%d",&x,&y,&z),ae(x,y,z);
	for(int i=1,x;i<=m;i++)scanf("%d",&x),sp[x]=true;
	dfs1(1,0),dfs2(1,0);
//	for(int i=1;i<=n;i++)printf("%lld %d %lld\n",f[i],g[i],h[i]);
	for(int i=1;i<=n;i++)printf("%lld\n",2*f[i]-h[i]); 
	return 0;
}

posted @ 2021-03-31 14:33  Troverld  阅读(94)  评论(0编辑  收藏  举报