[WC2018] 通道

文章介绍了如何通过图论方法解决关于多组双向传送通道中,如何规划调查路径以最大化使用者数量总和的问题。
摘要由CSDN通过智能技术生成

[WC2018] 通道

题目背景

滥用本题评测将被封号。

题目描述

11328 年,C 国的科学家们研发了一种高速传送通道,可以在很短的时间内把居民从通道的一端送往另一端,这些通道都是双向的。

美中不足的是,这种传送通道需要进行大量的维护和检修。经过规划,C 国总统决定在 M 城中新建这种通道,在 M 城中,建立了 \(n\) 个传送站和 \(3\times(n-1)\) 条传送通道,这些传送通道被分为 \(3\) 组,每一组都包含了 \((n-1)\) 条通道。

当任意一组通道运行时,居民都可以通过这组通道从任意一个传送站前往任意的另一个传送站。也就是说,所有的传送站都会被通道所连通。

三组通道按照 \(1\)\(2\)\(3\) 的顺序轮流运行,循环反复。在任意一个时刻,都有且只有一组传送通道可以使用。形式化地,在第 \(i\) 天中,有且只有第 \(((i-1)\bmod 3+1)\) 组通道运行。

C 国著名科学家 Access Globe 正在进行一项社会调查实验:调查两个传送站之间的传送通道使用者的信息。 Access Globe 的计划是这样的:

  • 选定两个传送站 \(a, b\)
  • 第一天,他从 \(a\) 出发,使用正在运行的这组通道沿最短路径到达 \(b\),并调查经过的所有通道上使用者的信息
  • 第二天,他从 \(b\) 出发,使用正在运行的这组通道沿最短路径到达 \(a\),并调查经过的所有通道上使用者的信息
  • 第三天,他从 \(a\) 出发,使用正在运行的这组通道沿最短路径到达 \(b\),并调查经过的所有通道上使用者的信息

Access Globe 知道每一条传输线路在运行时的使用者人数。他希望找出一对 \(a, b\),使得在整个实验过程中所有经过的通道的使用者数量之和最大。

Access Globe 希望参加 CCF NOI 2018 冬令营的你帮他解决这个简单的小问题。如果你成功地解决了这个问题, Access Globe 会送你一份小礼物——\(100\) 分!

输入格式

输入文件的第 \(1\) 行包含一个正整数 \(n\),表示传送站的个数,传送站从 \(1\)\(n\) 编号;

输入文件的第 \(2\) 到第 \(n\) 行,每行包含 \(3\) 个数 \(u,v,w\),表示第一组通道中有一条连接 \(u,v\) 的通道,其运行时使用者数量为 \(w\) 人;

输入文件的第 \((n+1)\) 到第 \((2n-1)\) 行,每行包含 \(3\) 个数 \(u,v,w\),表示第二组通道中有一条连接 \(u,v\) 的通道,其运行时使用者数量为 \(w\) 人;

输入文件的第 \(2n\) 到第 \((3n-2)\) 行,每行包含 \(3\) 个数 \(u,v,w\),表示第三组通道中有一条连接 \(u,v\) 的通道,其运行时使用者数量为 \(w\) 人。

输出格式

输出文件共 \(1\) 行,包含一个整数,表示最大的使用者数量之和。

样例 #1

样例输入 #1

5
1 2 2
1 3 0
1 4 1
4 5 7
1 2 0
2 3 1
2 4 1
2 5 3
1 5 2
2 3 8
3 4 5
4 5 1

样例输出 #1

27

提示

【样例\(1\)说明】

下图为样例中 \(M\) 城的传送站和传输线路情况。其中点和虚线交替的线条、虚线条和实线条分别表示第一组、第二组和第三组通道。QQ20180209195844.png
一种可行的方案是选择 \(a=2,b=5\),这样的使用者数量之和为 \((3)+(8+5+1)+(2+1+7)=27\)

【子任务】

对于所有数据, \(2 \leq n \leq 10^5,0 \leq w \leq 10^{12}\)

特殊性质 \(0\):任意两组通道构成完全相同。

特殊性质 \(1\):第二组通道和第三组通道构成完全相同。

特殊性质 \(2\):对于第二组的每一个传送站,最多只有两个通道可以到达它,且编号为 \(x,y\) 的传送站之间通过一条通道直接连接充要条件是 \(|x-y|=1\)

特殊性质 \(3\):对于第三组的每一个传送站,最多只有两个通道可以到达它。

特殊性质 \(4\)\(n \leq 3000\)

QQ20180209201418.png
本题共 \(31\) 个测试点,每个子任务对应测试点如下:

  • 子任务 \(0\) 对应测试点 \(1-7\)
  • 子任务 \(1\) 对应测试点 \(8\)
  • 子任务 \(2\) 对应测试点 \(9-11\)
  • 子任务 \(3\) 对应测试点 \(12-14\)
  • 子任务 \(4\) 对应测试点 \(15-17\)
  • 子任务 \(5\) 对应测试点 \(18-21\)
  • 子任务 \(6\) 对应测试点 \(22-25\)
  • 子任务 \(7\) 对应测试点 \(26-31\)

【提示】

  • 在两组通道中,可能都包含了连接传送站 \(x,y\) 的通道,此时我们认为这两条通道是不同的。
  • 特殊性质中,A 组通道和 B 组通道的『构成完全相同』是指:如果在 A 组中 \(u,v\) 之间存在一条使用人数为 \(w\) 的通道,那么在 B 组中 \(u,v\) 之间一定也存在一条使用人数为 \(w\) 的通道。是否相同与描述方式与描述顺序均无关。即在构成完全相同的两组通道 A 和 B 中,通道输入的顺序不一定相同,每条通道的端点的输入顺序也不一定相同(对于 A、B 组中一条连接 \(u,v\) 的使用人数为 \(w\) 的通道,一种可能出现的输入为: A 组通道中输入 \(u\ v\ w\),而 B 组通道中输入 \(v\ u\ w\))。

多棵树的问题,先考虑树分治。

因为我们要求最大的距离,所以树分治的时候必须强制统计答案的两个点在不同的子树内。考虑使用边分治,这样只用强制他们在两个集合内。设点 \(u\) 到达边分治某一边的距离为 \(d_u\)

然后套路的,对第二棵树建出虚树,对于两个在边分治不同集合的点 \(u,v\),他们的距离是 \(d_u+d_v+dis2(u,v)+dis3(u,v)\)。枚举一下两个点在第二棵树的lca点 \(p\),设第二棵树根到点 \(u\) 的距离为 \(g_u\),那么两个点的距离为 \(d_u+d_v+g_u+g_v+dis3(u,v)-2g_p\)

对于第三棵树,考虑使用直径的性质去找两个最远点。合并两个集合的直径时他们的并的直径两端点一定时某个集合直径的两端点。所以可以维护 \(h_u\) 表示在虚树的 \(u\) 子树中的关键点的直径,然后再合并直径的时候更新答案就行了。

如果用 \(O(1)lca\) 求第三棵树的距离,建虚树时对 dfn 序排序用归并排序的话,可以做到 \(O(nlogn)\)

#include<bits/stdc++.h>
using namespace std;
const int N=4e5+5;
typedef long long LL;
int n,lg[N],k,vs[N],h[N],rt,sz[N],v[N],mn,vp[N];
LL ans,d[N];
pair<int,int>fg;
vector<int>g[N]; 
struct edge{
	int v,nxt;
	LL w;
};
struct graph{
	edge e[N];
	int hd[N],idx,e_num,in[N];
	pair<int,int>st[20][N];
	LL dep[N];
	void add_edge(int u,int v,LL w)
	{
		e[++e_num]=(edge){v,hd[u],w};
		hd[u]=e_num;
		e[++e_num]=(edge){u,hd[v],w};
		hd[v]=e_num;
	}
	void dfs(int x,int y,pair<int,int> a[],int d)
	{
		st[0][++idx]=make_pair(d,x);
		in[x]=idx;
		for(int i=hd[x];i;i=e[i].nxt)
			if(e[i].v^y)
				dep[e[i].v]=dep[x]+e[i].w,dfs(e[i].v,x,a,d+1),st[0][++idx]=make_pair(d,x);
	}
	void build()
	{
		dfs(1,0,st[0],0);
		for(int i=1;i<20;i++)
			for(int j=1;j+(1<<i)-1<=idx;j++)
				st[i][j]=min(st[i-1][j],st[i-1][j+(1<<i-1)]);
	}
	int lca(int x,int y)
	{
		int l=in[x],r=in[y],k;
		if(l>r)
			swap(l,r);
		k=lg[r-l+1];
		return min(st[k][l],st[k][r-(1<<k)+1]).second;
	}
	LL dis(int x,int y)
	{
		return dep[x]+dep[y]-2*dep[lca(x,y)];
	}
}a,b,c,p;
void dfs(int x,int y)
{
	int ls=x;
	for(int i=a.hd[x];i;i=a.e[i].nxt)
	{
		if(a.e[i].v==y)
			continue;
		p.add_edge(ls,a.e[i].v,a.e[i].w);
		p.add_edge(ls,++k,0);
		ls=k;
	}
	for(int i=a.hd[x];i;i=a.e[i].nxt)
		if(a.e[i].v^y)
			dfs(a.e[i].v,x);
}
void sou(int x,int y,int all)
{
	sz[x]=x<=n;
	for(int i=p.hd[x];i;i=p.e[i].nxt)
	{
		if(!v[p.e[i].v]&&p.e[i].v^y)
		{
			sou(p.e[i].v,x,all);
			if(max(sz[p.e[i].v],all-sz[p.e[i].v])<mn)
				mn=max(sz[p.e[i].v],all-sz[p.e[i].v]),fg=make_pair(x,p.e[i].v);
			sz[x]+=sz[p.e[i].v];
		}
	}
}
pair<int,int> findrt(int x,int n)
{
	mn=1000000000,fg=make_pair(x,x);
	sou(x,0,n);
	return fg;
}
int sou(int x,int y)
{
	int s=x<=n;
	for(int i=p.hd[x];i;i=p.e[i].nxt)
		if(p.e[i].v^y&&!v[p.e[i].v])
			s+=sou(p.e[i].v,x);
	return s;
}
LL ask(int u,int v)
{
	if(!u||!v)
		return 0;
	return c.dis(u,v)+b.dis(rt,u)+b.dis(rt,v)+d[u]+d[v];
}
pair<int,int>mge(pair<int,int>u,pair<int,int>v)
{
	if(!u.first)
		return v;
	if(!v.first)
		return u;
	pair<int,int>p=u;
	if(ask(v.first,v.second)>ask(p.first,p.second))
		p=v;
	if(ask(v.first,u.second)>ask(p.first,p.second))
		p=make_pair(v.first,u.second);
	if(ask(u.first,v.second)>ask(p.first,p.second))
		p=make_pair(u.first,v.second);
	if(ask(u.first,v.first)>ask(p.first,p.second))
		p=make_pair(u.first,v.first);
	if(ask(u.second,v.second)>ask(p.first,p.second))
		p=make_pair(u.second,v.second);
	return p;
}
void add(int x,pair<pair<int,int>,pair<int,int> >&p,pair<pair<int,int>,pair<int,int> >q)
{
	ans=max(ans,max({ask(p.first.first,q.second.first),
	ask(p.first.first,q.second.second),
	ask(p.first.second,q.second.first),
	ask(p.first.second,q.second.second),
	ask(p.second.first,q.first.first),
	ask(p.second.first,q.first.second),
	ask(p.second.second,q.first.first),
	ask(p.second.second,q.first.second)
	})-2*b.dis(rt,x));
	p=make_pair(mge(p.first,q.first),mge(p.second,q.second));
}
pair<pair<int,int>,pair<int,int> >suo(int x,int y)
{
	pair<pair<int,int>,pair<int,int> >p,q;
	for(int j:g[x])
	{
		if(j^y)
		{
			q=suo(j,x);
			add(x,p,q);
		}
	}
	if(vs[x]==1)
		add(x,p,make_pair(make_pair(0,0),make_pair(x,x)));
	if(!vs[x])
		add(x,p,make_pair(make_pair(x,x),make_pair(0,0)));
	return p; 
}
void build(vector<int>p)
{
	static int st[N],tp;
	st[tp=1]=b.lca(p[0],p.back());
	for(int j:p)
	{
		sz[j]=0;
		if(j==st[1])
			continue;
		int d=b.lca(st[tp],j);
		if(d^st[tp])
		{
			while(b.in[d]<b.in[st[tp-1]])
				g[st[tp-1]].push_back(st[tp]),--tp;
			if(d^st[tp-1])
				g[d].push_back(st[tp]),st[tp]=d;
			else
				g[d].push_back(st[tp--]);
		}
		st[++tp]=j;
	}
	for(int i=1;i<tp;i++)
		g[st[i]].push_back(st[i+1]);
	rt=st[1];
	suo(st[1],0);
	for(int i=1;i<p.size();i++)
	{
		g[p[i]].clear();
		g[b.lca(p[i],p[i-1])].clear();
	}
}
void init(int x,int y)
{
	for(int i=p.hd[x];i;i=p.e[i].nxt)
		if(p.e[i].v^y&&!v[p.e[i].v])
			d[p.e[i].v]=d[x]+p.e[i].w,init(p.e[i].v,x);
}
vector<int>solve(pair<int,int>x,int n)
{
	vector<int>g;
	if(x.first==x.second)
	{
		if(x.first<=::n)
			g.push_back(x.first);
		return g;
	}
	v[x.second]=1;
	int l=sou(x.first,0),r=n-l;
	vector<int>fl=solve(findrt(x.first,l),l),fr;
	v[x.second]=0,v[x.first]=1;
	fr=solve(findrt(x.second,r),r);
	d[x.first]=v[x.first]=0;
	init(x.first,0);
	v[x.second]=0;
	for(int j:fl)
		vs[j]=1;
	for(int j:fr)
		vs[j]=0;
	l=0,r=0;
	while(l<fl.size()&&r<fr.size())
	{
		if(b.in[fl[l]]<b.in[fr[r]])
			g.push_back(fl[l++]);
		else
			g.push_back(fr[r++]);
	}
	while(l<fl.size())
		g.push_back(fl[l++]);
	while(r<fr.size())
		g.push_back(fr[r++]);
//	printf("hjhloveqzm:%d %d\n",x.first,x.second);
//	for(int j:g)
//		printf("%d ",j);
//	puts("");
	if(!g.empty())
		build(g);
	for(int j:g)
		vs[j]=-1;
	v[x.second]=0;
	return g;
}
int main()
{
	memset(vs,-1,sizeof(vs));
	int u,v;
	LL w;
	for(int i=2;i<N;i++)
		lg[i]=lg[i>>1]+1;
	scanf("%d",&n);
	for(int i=1;i<n;i++)
	{
		scanf("%d%d%lld",&u,&v,&w);
		a.add_edge(u,v,w); 
	}
	for(int i=1;i<n;i++)
	{
		scanf("%d%d%lld",&u,&v,&w);
		b.add_edge(u,v,w); 
	}
	for(int i=1;i<n;i++)
	{
		scanf("%d%d%lld",&u,&v,&w);
		c.add_edge(u,v,w);
	}
	b.build(),c.build();
	k=n;
	dfs(1,0);
	solve(findrt(1,n),n);
	printf("%lld",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值